From 3ec9f9722d8cfaf6b0000028df2170845a7adc78 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Tue, 25 Dec 2018 16:35:26 -0500 Subject: [PATCH 001/119] enable raw re pattern --- .vscode/settings.json | 3 ++- slugify/slugify.py | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 20d15cb..1311c75 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "python.linting.pylintEnabled": false + "python.linting.pylintEnabled": false, + "restructuredtext.confPath": "" } \ No newline at end of file diff --git a/slugify/slugify.py b/slugify/slugify.py index 192bbd3..8569233 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -21,14 +21,14 @@ __all__ = ['slugify', 'smart_truncate'] -CHAR_ENTITY_PATTERN = re.compile('&(%s);' % '|'.join(name2codepoint)) -DECIMAL_PATTERN = re.compile('&#(\d+);') -HEX_PATTERN = re.compile('&#x([\da-fA-F]+);') +CHAR_ENTITY_PATTERN = re.compile(r'&(%s);' % '|'.join(name2codepoint)) +DECIMAL_PATTERN = re.compile(r'&#(\d+);') +HEX_PATTERN = re.compile(r'&#x([\da-fA-F]+);') QUOTE_PATTERN = re.compile(r'[\']+') ALLOWED_CHARS_PATTERN = re.compile(r'[^-a-z0-9]+') ALLOWED_CHARS_PATTERN_WITH_UPPERCASE = re.compile(r'[^-a-zA-Z0-9]+') -DUPLICATE_DASH_PATTERN = re.compile('-{2,}') -NUMBERS_PATTERN = re.compile('(?<=\d),(?=\d)') +DUPLICATE_DASH_PATTERN = re.compile(r'-{2,}') +NUMBERS_PATTERN = re.compile(r'(?<=\d),(?=\d)') DEFAULT_SEPARATOR = '-' From 9bd06cd6fe5e744a935a0c6faf45900961832cc8 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Tue, 25 Dec 2018 16:57:21 -0500 Subject: [PATCH 002/119] conditional text_unidecode install --- setup.py | 10 ++++++---- slugify/__init__.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 3e2a490..ab59194 100755 --- a/setup.py +++ b/setup.py @@ -7,6 +7,11 @@ import sys import codecs +install_requires = [] +try: + import text_unidecode +except ImportError: + install_requires.append('Unidecode>=0.04.16') name = 'python-slugify' package = 'slugify' @@ -15,10 +20,7 @@ author = 'Val Neekman' author_email = 'info@neekware.com' license = 'MIT' -if "SLUGIFY_USES_TEXT_UNIDECODE" in os.environ: - install_requires = ['text-unidecode>=1.2'] -else: - install_requires = ['Unidecode>=0.04.16'] + classifiers = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', diff --git a/slugify/__init__.py b/slugify/__init__.py index 1a02a3e..81849b9 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -3,4 +3,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '1.2.6' +__version__ = '2.0.0' From 96c57f75d4cf11f68a895169fb3a78952d068657 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Tue, 25 Dec 2018 17:35:10 -0500 Subject: [PATCH 003/119] update readme, changelog, manifest --- .vscode/settings.json | 3 +- CHANGELOG.md | 3 ++ MANIFEST.in | 2 +- README.rst => README.md | 113 ++++++++++++++++------------------------ 4 files changed, 51 insertions(+), 70 deletions(-) rename README.rst => README.md (64%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1311c75..32531ea 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "python.linting.pylintEnabled": false, - "restructuredtext.confPath": "" + "restructuredtext.confPath": "", + "python.pythonPath": "/usr/local/opt/python/bin/python3.6" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 84f3436..cd80bf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 2.0.0 + - Fix alternative dependency installation + ## 1.2.6 - Add support for case sensitive slugs (@s-m-e) diff --git a/MANIFEST.in b/MANIFEST.in index a755e2e..067e13a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ include CHANGELOG.md include LICENSE -include README.rst +include README.md diff --git a/README.rst b/README.md similarity index 64% rename from README.rst rename to README.md index e02375f..bf76a84 100644 --- a/README.rst +++ b/README.md @@ -1,61 +1,43 @@ Python Slugify -============== +==================== -|status-image| |version-image| |coverage-image| +**A Python slugify application that handles unicode**. -Overview --------- - -A Python **slugify** application that handles **unicode**. - - -How to install --------------- - -Via ``pip``: - -.. code:: bash - - $ pip install python-slugify - -Via ``easy_install``: - -.. code:: bash - - $ easy_install python-slugify - -From sources via ``git``: +[![status-image]][status-link] +[![version-image]][version-link] +[![coverage-image]][coverage-link] -.. code:: bash - - $ git clone http://github.com/un33k/python-slugify - $ cd python-slugify - $ python setup.py install +Overview +==================== -From sources: +**Best attempt** to unicode strings while keeping it **DRY**. -.. code:: bash +Notice +==================== - $ wget https://github.com/un33k/python-slugify/zipball/master - # unzip the downloaded file - # cd into python-slugify-* directory - $ python setup.py install +By default, this modules installs and uses [Unidecode](https://github.com/avian2/unidecode) *(GPL)* for its decoding needs. However if you wish to use [text-unidecode](https://github.com/kmike/text-unidecode) *(GPL & Perl Artistic)* instead, ensure it is installed prior to `python-slugify` installation. -Note: +In cases where both `Unidecode` and `text-unidecode` are installed, the `Unidecode` is used as the default decoding module. -By default *python-slugify* installs **unidecode** (GPL) for its decoding needs. -Alternatively *python-slugify* can install and use **text-unidecode** (GPL & Perl Artistic) instead. This is done by setting up -an environment variable *SLUGIFY_USES_TEXT_UNIDECODE=yes* prior to installing and/or upgrading `python-slugify`. +How to install +==================== -In cases where both **unidecode** and **text-unidecode** are installed, *python-slugify* always defaults to using **unidecode** regardless of the *SLUGIFY_USES_TEXT_UNIDECODE=yes* environment variable. + 1. easy_install python-slugify + 2. pip install python-slugify + 3. git clone http://github.com/un33k/python-slugify + a. cd python-slugify + b. python setup.py install + 4. wget https://github.com/un33k/python-slugify/zipball/master + a. unzip the downloaded file + b. cd python-slugify-* + c. python setup.py install How to use ----------- - -.. code:: python +==================== + ```python from slugify import slugify txt = "This is a test ---" @@ -180,52 +162,47 @@ How to use r = slugify(txt, separator='_', regex_pattern=regex_pattern) self.assertNotEqual(r, "_this_is_a_test_") -For more examples, have a look at the (`TEST`_) file. + ``` + +For more examples, have a look at the [test.py](test.py) file. + Running the tests ------------------ +==================== To run the tests against the current environment: -.. code:: bash - python test.py License -------- +==================== -Released under a (`MIT`_) license. - -**Note:** - -*python-slugify* relies on thirdparty **API** for decoding unicode strings. This dependency is kept at the public **API** ONLY in -order to ensure that *python-slugify* never becomes a **derivative work** of any other packages. MIT license holds. +Released under a ([MIT](LICENSE)) license. Version -------- - +==================== X.Y.Z Version -:: - `MAJOR` version -- when you make incompatible API changes, `MINOR` version -- when you add functionality in a backwards-compatible manner, and `PATCH` version -- when you make backwards-compatible bug fixes. -.. |status-image| image:: https://secure.travis-ci.org/un33k/python-slugify.png?branch=master - :target: http://travis-ci.org/un33k/python-slugify?branch=master +[status-image]: https://secure.travis-ci.org/un33k/python-slugify.png?branch=master +[status-link]: http://travis-ci.org/un33k/python-slugify?branch=master + +[version-image]: https://img.shields.io/pypi/v/python-slugify.svg +[version-link]: https://pypi.python.org/pypi/python-slugify -.. |version-image| image:: https://img.shields.io/pypi/v/python-slugify.svg - :target: https://pypi.python.org/pypi/python-slugify +[coverage-image]: https://coveralls.io/repos/un33k/python-slugify/badge.svg +[coverage-link]: https://coveralls.io/r/un33k/python-slugify -.. |coverage-image| image:: https://coveralls.io/repos/un33k/python-slugify/badge.svg - :target: https://coveralls.io/r/un33k/python-slugify +[download-image]: https://img.shields.io/pypi/dm/python-slugify.svg +[download-link]: https://pypi.python.org/pypi/python-slugify -.. |download-image| image:: https://img.shields.io/pypi/dm/python-slugify.svg - :target: https://pypi.python.org/pypi/python-slugify -.. _MIT: https://github.com/un33k/python-slugify/blob/master/LICENSE +Sponsors +==================== -.. _TEST: https://github.com/un33k/python-slugify/blob/master/test.py +[![Surge](https://www.surgeforward.com/wp-content/themes/understrap-master/images/logo.png)](https://github.com/surgeforward) \ No newline at end of file From 21e05ad00e354fe0295228acc8ac0ca4300558ad Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Tue, 25 Dec 2018 17:37:43 -0500 Subject: [PATCH 004/119] update ci --- .travis.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4bf2dd4..b945819 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,13 +10,8 @@ python: - "3.6" - pypy -env: - - SLUGIFY_USES_TEXT_UNIDECODE=yes - - SLUGIFY_USES_UNIDECODE=yes # dummy: tell travis to run more than one build types - install: - pip install pip -U - - if [[ -z "${SLUGIFY_USES_TEXT_UNIDECODE}" ]]; then pip install -q -r requirements.txt; else pip install -q -r requirements_alt.txt; fi - pip install -e . - pip install pycodestyle - pip install coveralls From 2990b70f32ad4482552ba7b7d72a71e94d0ee05f Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Tue, 25 Dec 2018 17:38:57 -0500 Subject: [PATCH 005/119] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bf76a84..6294498 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Python Slugify Overview ==================== -**Best attempt** to unicode strings while keeping it **DRY**. +**Best attempt** to create slugs from unicode strings while keeping it **DRY**. Notice ==================== From 1df7ffe39998d58e6fb521c9a95adc4df67f6918 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Tue, 25 Dec 2018 17:40:58 -0500 Subject: [PATCH 006/119] drop test for py 2.6 and 3.3 --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b945819..685386f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,7 @@ sudo: false language: python python: - - "2.6" - "2.7" - - "3.3" - "3.4" - "3.5" - "3.6" From 1ce42c6c9f1bb88b8a195851176f16f07609b322 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Tue, 25 Dec 2018 17:44:53 -0500 Subject: [PATCH 007/119] clean up readme --- README.md | 64 ------------------------------------------------------- 1 file changed, 64 deletions(-) diff --git a/README.md b/README.md index 6294498..e1a03cc 100644 --- a/README.md +++ b/README.md @@ -44,18 +44,6 @@ How to use r = slugify(txt) self.assertEqual(r, "this-is-a-test") - txt = "___This is a test ---" - r = slugify(txt) - self.assertEqual(r, "this-is-a-test") - - txt = "___This is a test___" - r = slugify(txt) - self.assertEqual(r, "this-is-a-test") - - txt = "This -- is a ## test ---" - r = slugify(txt) - self.assertEqual(r, "this-is-a-test") - txt = '影師嗎' r = slugify(txt) self.assertEqual(r, "ying-shi-ma") @@ -68,10 +56,6 @@ How to use r = slugify(txt) self.assertEqual(r, "nin-hao-wo-shi-zhong-guo-ren") - txt = 'jaja---lol-méméméoo--a' - r = slugify(txt) - self.assertEqual(r, "jaja-lol-mememeoo-a") - txt = 'Компьютер' r = slugify(txt) self.assertEqual(r, "kompiuter") @@ -80,30 +64,10 @@ How to use r = slugify(txt, max_length=9) self.assertEqual(r, "jaja-lol") - txt = 'jaja---lol-méméméoo--a' - r = slugify(txt, max_length=15) - self.assertEqual(r, "jaja-lol-mememe") - - txt = 'jaja---lol-méméméoo--a' - r = slugify(txt, max_length=50) - self.assertEqual(r, "jaja-lol-mememeoo-a") - txt = 'jaja---lol-méméméoo--a' r = slugify(txt, max_length=15, word_boundary=True) self.assertEqual(r, "jaja-lol-a") - txt = 'jaja---lol-méméméoo--a' - r = slugify(txt, max_length=17, word_boundary=True) - self.assertEqual(r, "jaja-lol-mememeoo") - - txt = 'jaja---lol-méméméoo--a' - r = slugify(txt, max_length=18, word_boundary=True) - self.assertEqual(r, "jaja-lol-mememeoo") - - txt = 'jaja---lol-méméméoo--a' - r = slugify(txt, max_length=19, word_boundary=True) - self.assertEqual(r, "jaja-lol-mememeoo-a") - txt = 'jaja---lol-méméméoo--a' r = slugify(txt, max_length=20, word_boundary=True, separator=".") self.assertEqual(r, "jaja.lol.mememeoo.a") @@ -112,34 +76,10 @@ How to use r = slugify(txt, max_length=13, word_boundary=True, save_order=True) self.assertEqual(r, "one-two-three") - txt = 'one two three four five' - r = slugify(txt, max_length=13, word_boundary=True, save_order=False) - self.assertEqual(r, "one-two-three") - - txt = 'one two three four five' - r = slugify(txt, max_length=12, word_boundary=True, save_order=False) - self.assertEqual(r, "one-two-four") - - txt = 'one two three four five' - r = slugify(txt, max_length=12, word_boundary=True, save_order=True) - self.assertEqual(r, "one-two") - - txt = 'this has a stopword' - r = slugify(txt, stopwords=['stopword']) - self.assertEqual(r, 'this-has-a') - txt = 'the quick brown fox jumps over the lazy dog' r = slugify(txt, stopwords=['the']) self.assertEqual(r, 'quick-brown-fox-jumps-over-lazy-dog') - txt = 'Foo A FOO B foo C' - r = slugify(txt, stopwords=['foo']) - self.assertEqual(r, 'a-b-c') - - txt = 'Foo A FOO B foo C' - r = slugify(txt, stopwords=['FOO']) - self.assertEqual(r, 'a-b-c') - txt = 'the quick brown fox jumps over the lazy dog in a hurry' r = slugify(txt, stopwords=['the', 'in', 'a', 'hurry']) self.assertEqual(r, 'quick-brown-fox-jumps-over-lazy-dog') @@ -148,10 +88,6 @@ How to use r = slugify(txt, stopwords=['Stopword'], lowercase=False) self.assertEqual(r, 'thIs-Has-a-stopword') - txt = 'foo & bar' - r = slugify(txt) - self.assertEqual(r, 'foo-bar') - txt = "___This is a test___" regex_pattern = r'[^-a-z0-9_]+' r = slugify(txt, regex_pattern=regex_pattern) From 66d856996073b015e111e337eb6b604d6bc08b5b Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Tue, 25 Dec 2018 17:46:06 -0500 Subject: [PATCH 008/119] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1a03cc..96116e8 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Overview Notice ==================== -By default, this modules installs and uses [Unidecode](https://github.com/avian2/unidecode) *(GPL)* for its decoding needs. However if you wish to use [text-unidecode](https://github.com/kmike/text-unidecode) *(GPL & Perl Artistic)* instead, ensure it is installed prior to `python-slugify` installation. +By default, this modules installs and uses [Unidecode](https://github.com/avian2/unidecode) *(GPL)* for its decoding needs. However if you wish to use [text-unidecode](https://github.com/kmike/text-unidecode) *(GPL & Perl Artistic)* instead, plesea ensure it is installed prior to `python-slugify` installation. In cases where both `Unidecode` and `text-unidecode` are installed, the `Unidecode` is used as the default decoding module. From 646761e5b4c73b9be7285c60eab4e10c30fe32f4 Mon Sep 17 00:00:00 2001 From: Andriy Orehov Date: Thu, 3 Jan 2019 18:28:01 +0200 Subject: [PATCH 009/119] add support user-specific replacements (#66) thx --- README.md | 4 ++++ slugify/slugify.py | 9 ++++++++- test.py | 9 +++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d58daee..bc4d9e8 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,10 @@ How to use r = slugify(txt, separator='_', regex_pattern=regex_pattern) self.assertNotEqual(r, "_this_is_a_test_") + txt = '10 | 20 %' + r = slugify(txt, replacements=[['|', 'or'], ['%', 'percent']]) + self.assertEqual(r, "10-or-20-percent") + ``` For more examples, have a look at the [test.py](test.py) file. diff --git a/slugify/slugify.py b/slugify/slugify.py index ccd33a8..1544aba 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -75,7 +75,8 @@ def smart_truncate(string, max_length=0, word_boundary=False, separator=' ', sav def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, word_boundary=False, - separator=DEFAULT_SEPARATOR, save_order=False, stopwords=(), regex_pattern=None, lowercase=True): + separator=DEFAULT_SEPARATOR, save_order=False, stopwords=(), regex_pattern=None, lowercase=True, + replacements=()): """ Make a slug from the given text. :param text (str): initial text @@ -89,9 +90,15 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w :param stopwords (iterable): words to discount :param regex_pattern (str): regex pattern for allowed characters :param lowercase (bool): activate case sensitivity by setting it to False + :param replacements (iterable): list of replacement rules e.g. [['|', 'or'], ['%', 'percent']] :return (str): """ + # user-specific replacements + if replacements: + for old, new in replacements: + text = text.replace(old, new) + # ensure text is unicode if not isinstance(text, _unicode_type): text = _unicode(text, 'utf-8', 'ignore') diff --git a/test.py b/test.py index 9ff9ec0..78b1956 100644 --- a/test.py +++ b/test.py @@ -189,6 +189,15 @@ def test_regex_pattern_keep_underscore_with_underscore_as_separator(self): r = slugify(txt, separator='_', regex_pattern=regex_pattern) self.assertNotEqual(r, "_this_is_a_test_") + def test_replacements(self): + txt = '10 | 20 %' + r = slugify(txt, replacements=[['|', 'or'], ['%', 'percent']]) + self.assertEqual(r, "10-or-20-percent") + + txt = 'I ♥ 🦄' + r = slugify(txt, replacements=[['♥', 'amour'], ['🦄', 'licorne']]) + self.assertEqual(r, "i-amour-licorne") + class TestUtils(unittest.TestCase): From 8aea5c49b960b66e5c81bf20f17ec23e41c8d157 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Thu, 3 Jan 2019 17:40:15 -0500 Subject: [PATCH 010/119] clean up, up version --- CHANGELOG.md | 3 +++ slugify/__init__.py | 2 +- slugify/slugify.py | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd80bf1..e2eed12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 2.0.1 + - Add replacements option e.g. [['|', 'or'], ['%', 'percent'], ['-', '_']] (@andriyor) + ## 2.0.0 - Fix alternative dependency installation diff --git a/slugify/__init__.py b/slugify/__init__.py index 81849b9..7358b99 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -3,4 +3,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '2.0.0' +__version__ = '2.0.1' diff --git a/slugify/slugify.py b/slugify/slugify.py index 1544aba..59e9672 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -165,6 +165,11 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w words = [w for w in text.split(DEFAULT_SEPARATOR) if w not in stopwords] text = DEFAULT_SEPARATOR.join(words) + # finalize user-specific replacements + if replacements: + for old, new in replacements: + text = text.replace(old, new) + # smart truncate if requested if max_length > 0: text = smart_truncate(text, max_length, word_boundary, DEFAULT_SEPARATOR, save_order) From 7addc366c4187d558bce3c719333936a6974fcc1 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Fri, 22 Feb 2019 17:58:56 -0500 Subject: [PATCH 011/119] add text-unidecode option as extra --- CHANGELOG.md | 4 ++++ README.md | 5 ++--- setup.py | 9 +++------ slugify/__init__.py | 2 +- slugify/slugify.py | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2eed12..f4f37a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.0 + - Upgrade Unidecode + - Allow install of text-unidecode as extra. "pip install python-slugify[text-unidecode]" + ## 2.0.1 - Add replacements option e.g. [['|', 'or'], ['%', 'percent'], ['-', '_']] (@andriyor) diff --git a/README.md b/README.md index bc4d9e8..8d0ce73 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,8 @@ Overview Notice ==================== -By default, this modules installs and uses [Unidecode](https://github.com/avian2/unidecode) *(GPL)* for its decoding needs. However if you wish to use [text-unidecode](https://github.com/kmike/text-unidecode) *(GPL & Perl Artistic)* instead, please ensure it is installed prior to `python-slugify` installation. - -In cases where both `Unidecode` and `text-unidecode` are installed, `Unidecode` is used as the default decoding module. +By default, this modules installs and uses [Unidecode](https://github.com/avian2/unidecode) *(GPL)* for its decoding needs. However if you wish to use [text-unidecode](https://github.com/kmike/text-unidecode) *(GPL & Perl Artistic)* instead, you must +install it as `python-slugify[text-unidecode]`. How to install diff --git a/setup.py b/setup.py index ab59194..5a8a5ce 100755 --- a/setup.py +++ b/setup.py @@ -7,12 +7,6 @@ import sys import codecs -install_requires = [] -try: - import text_unidecode -except ImportError: - install_requires.append('Unidecode>=0.04.16') - name = 'python-slugify' package = 'slugify' description = 'A Python Slugify application that handles Unicode' @@ -20,6 +14,8 @@ author = 'Val Neekman' author_email = 'info@neekware.com' license = 'MIT' +install_requires = ['Unidecode==1.0.23'] +extras_require = {'text-unidecode': ['text-unidecode==1.2']} classifiers = [ 'Development Status :: 5 - Production/Stable', @@ -70,6 +66,7 @@ def get_version(package): author_email=author_email, packages=find_packages(exclude=EXCLUDE_FROM_PACKAGES), install_requires=install_requires, + extras_require=extras_require, classifiers=classifiers, entry_points={'console_scripts': ['slugify=slugify.slugify:main']}, ) diff --git a/slugify/__init__.py b/slugify/__init__.py index 7358b99..c2e205b 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -3,4 +3,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '2.0.1' +__version__ = '3.0.0' diff --git a/slugify/slugify.py b/slugify/slugify.py index 59e9672..fcd1d22 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -14,9 +14,9 @@ unichr = chr try: - import unidecode -except ImportError: import text_unidecode as unidecode +except ImportError: + import unidecode __all__ = ['slugify', 'smart_truncate'] From 00e43c6b49a79ecf9acf35ec3cc606538d5b732a Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Mon, 25 Feb 2019 18:29:31 -0500 Subject: [PATCH 012/119] Upgrade Unidecode, add text-unidecode as extra option (#71) * Add replacements option (#67) * add text-unidecode option as extra --- CHANGELOG.md | 4 ++++ README.md | 5 ++--- setup.py | 9 +++------ slugify/__init__.py | 2 +- slugify/slugify.py | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2eed12..f4f37a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.0 + - Upgrade Unidecode + - Allow install of text-unidecode as extra. "pip install python-slugify[text-unidecode]" + ## 2.0.1 - Add replacements option e.g. [['|', 'or'], ['%', 'percent'], ['-', '_']] (@andriyor) diff --git a/README.md b/README.md index bc4d9e8..8d0ce73 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,8 @@ Overview Notice ==================== -By default, this modules installs and uses [Unidecode](https://github.com/avian2/unidecode) *(GPL)* for its decoding needs. However if you wish to use [text-unidecode](https://github.com/kmike/text-unidecode) *(GPL & Perl Artistic)* instead, please ensure it is installed prior to `python-slugify` installation. - -In cases where both `Unidecode` and `text-unidecode` are installed, `Unidecode` is used as the default decoding module. +By default, this modules installs and uses [Unidecode](https://github.com/avian2/unidecode) *(GPL)* for its decoding needs. However if you wish to use [text-unidecode](https://github.com/kmike/text-unidecode) *(GPL & Perl Artistic)* instead, you must +install it as `python-slugify[text-unidecode]`. How to install diff --git a/setup.py b/setup.py index ab59194..5a8a5ce 100755 --- a/setup.py +++ b/setup.py @@ -7,12 +7,6 @@ import sys import codecs -install_requires = [] -try: - import text_unidecode -except ImportError: - install_requires.append('Unidecode>=0.04.16') - name = 'python-slugify' package = 'slugify' description = 'A Python Slugify application that handles Unicode' @@ -20,6 +14,8 @@ author = 'Val Neekman' author_email = 'info@neekware.com' license = 'MIT' +install_requires = ['Unidecode==1.0.23'] +extras_require = {'text-unidecode': ['text-unidecode==1.2']} classifiers = [ 'Development Status :: 5 - Production/Stable', @@ -70,6 +66,7 @@ def get_version(package): author_email=author_email, packages=find_packages(exclude=EXCLUDE_FROM_PACKAGES), install_requires=install_requires, + extras_require=extras_require, classifiers=classifiers, entry_points={'console_scripts': ['slugify=slugify.slugify:main']}, ) diff --git a/slugify/__init__.py b/slugify/__init__.py index 7358b99..c2e205b 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -3,4 +3,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '2.0.1' +__version__ = '3.0.0' diff --git a/slugify/slugify.py b/slugify/slugify.py index 59e9672..fcd1d22 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -14,9 +14,9 @@ unichr = chr try: - import unidecode -except ImportError: import text_unidecode as unidecode +except ImportError: + import unidecode __all__ = ['slugify', 'smart_truncate'] From f6c7ead98a65bcf4047e629f0071591b18e70d99 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Mon, 25 Feb 2019 19:01:59 -0500 Subject: [PATCH 013/119] remove req.txt files --- requirements.txt | 1 - requirements_alt.txt | 1 - 2 files changed, 2 deletions(-) delete mode 100644 requirements.txt delete mode 100644 requirements_alt.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 656daab..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -Unidecode>=0.04.16 diff --git a/requirements_alt.txt b/requirements_alt.txt deleted file mode 100644 index 980e50a..0000000 --- a/requirements_alt.txt +++ /dev/null @@ -1 +0,0 @@ -text-unidecode>=1.2 \ No newline at end of file From 33d45a6c7ec3e3629bd45582cf65677beea1cf6c Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Tue, 26 Feb 2019 09:44:26 -0500 Subject: [PATCH 014/119] Optional Extra Requirements (#72) * Add replacements option (#67) * Upgrade Unidecode, add text-unidecode as extra option (#71) * Add replacements option (#67) * add text-unidecode option as extra * remove req.txt files --- CHANGELOG.md | 4 ++++ README.md | 5 ++--- requirements.txt | 1 - requirements_alt.txt | 1 - setup.py | 9 +++------ slugify/__init__.py | 2 +- slugify/slugify.py | 4 ++-- 7 files changed, 12 insertions(+), 14 deletions(-) delete mode 100644 requirements.txt delete mode 100644 requirements_alt.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index e2eed12..f4f37a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.0 + - Upgrade Unidecode + - Allow install of text-unidecode as extra. "pip install python-slugify[text-unidecode]" + ## 2.0.1 - Add replacements option e.g. [['|', 'or'], ['%', 'percent'], ['-', '_']] (@andriyor) diff --git a/README.md b/README.md index bc4d9e8..8d0ce73 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,8 @@ Overview Notice ==================== -By default, this modules installs and uses [Unidecode](https://github.com/avian2/unidecode) *(GPL)* for its decoding needs. However if you wish to use [text-unidecode](https://github.com/kmike/text-unidecode) *(GPL & Perl Artistic)* instead, please ensure it is installed prior to `python-slugify` installation. - -In cases where both `Unidecode` and `text-unidecode` are installed, `Unidecode` is used as the default decoding module. +By default, this modules installs and uses [Unidecode](https://github.com/avian2/unidecode) *(GPL)* for its decoding needs. However if you wish to use [text-unidecode](https://github.com/kmike/text-unidecode) *(GPL & Perl Artistic)* instead, you must +install it as `python-slugify[text-unidecode]`. How to install diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 656daab..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -Unidecode>=0.04.16 diff --git a/requirements_alt.txt b/requirements_alt.txt deleted file mode 100644 index 980e50a..0000000 --- a/requirements_alt.txt +++ /dev/null @@ -1 +0,0 @@ -text-unidecode>=1.2 \ No newline at end of file diff --git a/setup.py b/setup.py index ab59194..5a8a5ce 100755 --- a/setup.py +++ b/setup.py @@ -7,12 +7,6 @@ import sys import codecs -install_requires = [] -try: - import text_unidecode -except ImportError: - install_requires.append('Unidecode>=0.04.16') - name = 'python-slugify' package = 'slugify' description = 'A Python Slugify application that handles Unicode' @@ -20,6 +14,8 @@ author = 'Val Neekman' author_email = 'info@neekware.com' license = 'MIT' +install_requires = ['Unidecode==1.0.23'] +extras_require = {'text-unidecode': ['text-unidecode==1.2']} classifiers = [ 'Development Status :: 5 - Production/Stable', @@ -70,6 +66,7 @@ def get_version(package): author_email=author_email, packages=find_packages(exclude=EXCLUDE_FROM_PACKAGES), install_requires=install_requires, + extras_require=extras_require, classifiers=classifiers, entry_points={'console_scripts': ['slugify=slugify.slugify:main']}, ) diff --git a/slugify/__init__.py b/slugify/__init__.py index 7358b99..c2e205b 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -3,4 +3,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '2.0.1' +__version__ = '3.0.0' diff --git a/slugify/slugify.py b/slugify/slugify.py index 59e9672..fcd1d22 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -14,9 +14,9 @@ unichr = chr try: - import unidecode -except ImportError: import text_unidecode as unidecode +except ImportError: + import unidecode __all__ = ['slugify', 'smart_truncate'] From f98b091655824376327c6fa89c25c60e93e03de3 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Tue, 26 Feb 2019 11:37:59 -0500 Subject: [PATCH 015/119] use text-unidecode as primary decoding package --- CHANGELOG.md | 3 ++- README.md | 19 ++++++------------- setup.py | 4 ++-- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4f37a0..b5a37aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 3.0.0 - Upgrade Unidecode - - Allow install of text-unidecode as extra. "pip install python-slugify[text-unidecode]" + - Promote text-unidecode as the primary decoding package + - Add Unidecode as an optional extra. "pip install python-slugify[unidecode]" ## 2.0.1 - Add replacements option e.g. [['|', 'or'], ['%', 'percent'], ['-', '_']] (@andriyor) diff --git a/README.md b/README.md index 8d0ce73..13c94a7 100644 --- a/README.md +++ b/README.md @@ -15,23 +15,16 @@ Overview Notice ==================== -By default, this modules installs and uses [Unidecode](https://github.com/avian2/unidecode) *(GPL)* for its decoding needs. However if you wish to use [text-unidecode](https://github.com/kmike/text-unidecode) *(GPL & Perl Artistic)* instead, you must -install it as `python-slugify[text-unidecode]`. +This module, by default installs and uses [text-unidecode](https://github.com/kmike/text-unidecode) *(GPL & Perl Artistic)* for its decoding needs. + +However, there is an alternative decoding package called [Unidecode](https://github.com/avian2/unidecode) *(GPL)*. It can be installed as `python-slugify[unidecode]` for those who prefer it. How to install ==================== - - 1. easy_install python-slugify - 2. pip install python-slugify - 3. git clone http://github.com/un33k/python-slugify - a. cd python-slugify - b. python setup.py install - 4. wget https://github.com/un33k/python-slugify/zipball/master - a. unzip the downloaded file - b. cd python-slugify-* - c. python setup.py install - + easy_install python-slugify |OR| easy_install python-slugify[unidecode] + -- OR -- + pip install python-slugify |OR| pip install python-slugify[unidecode] How to use ==================== diff --git a/setup.py b/setup.py index 5a8a5ce..a78b672 100755 --- a/setup.py +++ b/setup.py @@ -14,8 +14,8 @@ author = 'Val Neekman' author_email = 'info@neekware.com' license = 'MIT' -install_requires = ['Unidecode==1.0.23'] -extras_require = {'text-unidecode': ['text-unidecode==1.2']} +install_requires = ['text-unidecode==1.2'] +extras_require = {'unidecode': ['Unidecode==1.0.23']} classifiers = [ 'Development Status :: 5 - Production/Stable', From 5c85c2aedd57ec5d40feef7fe9693f790dca18b3 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Tue, 26 Feb 2019 11:47:35 -0500 Subject: [PATCH 016/119] add dev reqs --- dev.requirements.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 dev.requirements.txt diff --git a/dev.requirements.txt b/dev.requirements.txt new file mode 100644 index 0000000..f87605e --- /dev/null +++ b/dev.requirements.txt @@ -0,0 +1 @@ +pycodestyle==2.5.0 \ No newline at end of file From b8be7d69119dcceb9a3e0ce64a509415737190ac Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Sun, 3 Mar 2019 12:40:56 -0500 Subject: [PATCH 017/119] Promote text-unidecode to primary decoding api (#73) * enable raw re pattern * conditional text_unidecode install * update readme, changelog, manifest * update ci * readme * drop test for py 2.6 and 3.3 * clean up readme * readme * add support user-specific replacements (#66) thx * clean up, up version * add text-unidecode option as extra * Upgrade Unidecode, add text-unidecode as extra option (#71) * Add replacements option (#67) * add text-unidecode option as extra * remove req.txt files * Optional Extra Requirements (#72) * Add replacements option (#67) * Upgrade Unidecode, add text-unidecode as extra option (#71) * Add replacements option (#67) * add text-unidecode option as extra * remove req.txt files * use text-unidecode as primary decoding package * add dev reqs --- CHANGELOG.md | 5 +++++ README.md | 18 +++++------------- dev.requirements.txt | 1 + requirements.txt | 1 - requirements_alt.txt | 1 - setup.py | 9 +++------ slugify/__init__.py | 2 +- slugify/slugify.py | 4 ++-- 8 files changed, 17 insertions(+), 24 deletions(-) create mode 100644 dev.requirements.txt delete mode 100644 requirements.txt delete mode 100644 requirements_alt.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index e2eed12..b5a37aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 3.0.0 + - Upgrade Unidecode + - Promote text-unidecode as the primary decoding package + - Add Unidecode as an optional extra. "pip install python-slugify[unidecode]" + ## 2.0.1 - Add replacements option e.g. [['|', 'or'], ['%', 'percent'], ['-', '_']] (@andriyor) diff --git a/README.md b/README.md index bc4d9e8..13c94a7 100644 --- a/README.md +++ b/README.md @@ -15,24 +15,16 @@ Overview Notice ==================== -By default, this modules installs and uses [Unidecode](https://github.com/avian2/unidecode) *(GPL)* for its decoding needs. However if you wish to use [text-unidecode](https://github.com/kmike/text-unidecode) *(GPL & Perl Artistic)* instead, please ensure it is installed prior to `python-slugify` installation. +This module, by default installs and uses [text-unidecode](https://github.com/kmike/text-unidecode) *(GPL & Perl Artistic)* for its decoding needs. -In cases where both `Unidecode` and `text-unidecode` are installed, `Unidecode` is used as the default decoding module. +However, there is an alternative decoding package called [Unidecode](https://github.com/avian2/unidecode) *(GPL)*. It can be installed as `python-slugify[unidecode]` for those who prefer it. How to install ==================== - - 1. easy_install python-slugify - 2. pip install python-slugify - 3. git clone http://github.com/un33k/python-slugify - a. cd python-slugify - b. python setup.py install - 4. wget https://github.com/un33k/python-slugify/zipball/master - a. unzip the downloaded file - b. cd python-slugify-* - c. python setup.py install - + easy_install python-slugify |OR| easy_install python-slugify[unidecode] + -- OR -- + pip install python-slugify |OR| pip install python-slugify[unidecode] How to use ==================== diff --git a/dev.requirements.txt b/dev.requirements.txt new file mode 100644 index 0000000..f87605e --- /dev/null +++ b/dev.requirements.txt @@ -0,0 +1 @@ +pycodestyle==2.5.0 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 656daab..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -Unidecode>=0.04.16 diff --git a/requirements_alt.txt b/requirements_alt.txt deleted file mode 100644 index 980e50a..0000000 --- a/requirements_alt.txt +++ /dev/null @@ -1 +0,0 @@ -text-unidecode>=1.2 \ No newline at end of file diff --git a/setup.py b/setup.py index ab59194..a78b672 100755 --- a/setup.py +++ b/setup.py @@ -7,12 +7,6 @@ import sys import codecs -install_requires = [] -try: - import text_unidecode -except ImportError: - install_requires.append('Unidecode>=0.04.16') - name = 'python-slugify' package = 'slugify' description = 'A Python Slugify application that handles Unicode' @@ -20,6 +14,8 @@ author = 'Val Neekman' author_email = 'info@neekware.com' license = 'MIT' +install_requires = ['text-unidecode==1.2'] +extras_require = {'unidecode': ['Unidecode==1.0.23']} classifiers = [ 'Development Status :: 5 - Production/Stable', @@ -70,6 +66,7 @@ def get_version(package): author_email=author_email, packages=find_packages(exclude=EXCLUDE_FROM_PACKAGES), install_requires=install_requires, + extras_require=extras_require, classifiers=classifiers, entry_points={'console_scripts': ['slugify=slugify.slugify:main']}, ) diff --git a/slugify/__init__.py b/slugify/__init__.py index 7358b99..c2e205b 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -3,4 +3,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '2.0.1' +__version__ = '3.0.0' diff --git a/slugify/slugify.py b/slugify/slugify.py index 59e9672..fcd1d22 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -14,9 +14,9 @@ unichr = chr try: - import unidecode -except ImportError: import text_unidecode as unidecode +except ImportError: + import unidecode __all__ = ['slugify', 'smart_truncate'] From ee1741bede09cf712dcdc3d6d213f8b4d00d3688 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Tue, 26 Mar 2019 05:21:41 +0700 Subject: [PATCH 018/119] MANIFEST.in: Add test.py to sdist (#74) --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 067e13a..0c78f18 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include CHANGELOG.md include LICENSE include README.md +include test.py From 98c83bee0900751d37fd70c1a048cdd85ca034c4 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Mon, 25 Mar 2019 18:23:35 -0400 Subject: [PATCH 019/119] add test to manifest --- CHANGELOG.md | 3 +++ slugify/__init__.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5a37aa..a426d10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 3.0.1 + - Add test.py to manifest + ## 3.0.0 - Upgrade Unidecode - Promote text-unidecode as the primary decoding package diff --git a/slugify/__init__.py b/slugify/__init__.py index c2e205b..3ad56fe 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -3,4 +3,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '3.0.0' +__version__ = '3.0.1' From cab1c969e8bed86ab03f61dd04c90c376267988d Mon Sep 17 00:00:00 2001 From: Steven Loria Date: Sun, 31 Mar 2019 09:43:39 -0400 Subject: [PATCH 020/119] Test against Python 3.7 (#75) Also, remove `sudo: false` because TravisCI has migrated to their VM-based infrastructure: https://blog.travis-ci.com/2018-11-19-required-linux-infrastructure-migration --- .travis.yml | 5 +++-- setup.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 685386f..695c4b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,13 @@ -sudo: false language: python +dist: xenial python: - "2.7" - "3.4" - "3.5" - "3.6" - - pypy + - "3.7" + - "pypy2.7-6.0" install: - pip install pip -U diff --git a/setup.py b/setup.py index a78b672..4800173 100755 --- a/setup.py +++ b/setup.py @@ -32,6 +32,7 @@ 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', ] From 22515690d3edc4a93b0f0a4524019a87814b51af Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Sun, 31 Mar 2019 09:51:33 -0400 Subject: [PATCH 021/119] up version --- CHANGELOG.md | 3 +++ slugify/__init__.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a426d10..82ed7ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 3.0.2 + - Add official support of Py 3.7 + ## 3.0.1 - Add test.py to manifest diff --git a/slugify/__init__.py b/slugify/__init__.py index 3ad56fe..0859113 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -3,4 +3,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '3.0.1' +__version__ = '3.0.2' From a0cd8fd1608ff63b150bac9c6b32786a00bffc80 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Sun, 31 Mar 2019 09:59:54 -0400 Subject: [PATCH 022/119] add pypy3.5 test --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 695c4b9..c821af2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ python: - "3.6" - "3.7" - "pypy2.7-6.0" + - "pypy3.5-6.0" install: - pip install pip -U From 4ed9500f00c02d2429f1e0aaa236c28db0d23a8b Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Sun, 31 Mar 2019 10:14:44 -0400 Subject: [PATCH 023/119] Add Python 3.7 Support (#77) * enable raw re pattern * conditional text_unidecode install * update readme, changelog, manifest * update ci * readme * drop test for py 2.6 and 3.3 * clean up readme * readme * add support user-specific replacements (#66) thx * clean up, up version * add text-unidecode option as extra * Upgrade Unidecode, add text-unidecode as extra option (#71) * Add replacements option (#67) * add text-unidecode option as extra * remove req.txt files * Optional Extra Requirements (#72) * Add replacements option (#67) * Upgrade Unidecode, add text-unidecode as extra option (#71) * Add replacements option (#67) * add text-unidecode option as extra * remove req.txt files * use text-unidecode as primary decoding package * add dev reqs * up version * add pypy3.5 test --- .travis.yml | 1 + CHANGELOG.md | 3 +++ slugify/__init__.py | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 695c4b9..c821af2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ python: - "3.6" - "3.7" - "pypy2.7-6.0" + - "pypy3.5-6.0" install: - pip install pip -U diff --git a/CHANGELOG.md b/CHANGELOG.md index a426d10..82ed7ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 3.0.2 + - Add official support of Py 3.7 + ## 3.0.1 - Add test.py to manifest diff --git a/slugify/__init__.py b/slugify/__init__.py index 3ad56fe..0859113 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -3,4 +3,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '3.0.1' +__version__ = '3.0.2' From b6067d10fc140d30d5d8860f28758e56faa59b4e Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Sat, 27 Jul 2019 22:39:01 -0400 Subject: [PATCH 024/119] update readme, add pydoc --- README.md | 139 ++++++++++++++++++++++-------------- pycodestyle.sh => format.sh | 0 slugify/__init__.py | 2 +- slugify/slugify.py | 8 +-- test.py | 27 ++++++- 5 files changed, 118 insertions(+), 58 deletions(-) rename pycodestyle.sh => format.sh (100%) diff --git a/README.md b/README.md index 13c94a7..78c22d1 100644 --- a/README.md +++ b/README.md @@ -19,82 +19,117 @@ This module, by default installs and uses [text-unidecode](https://github.com/km However, there is an alternative decoding package called [Unidecode](https://github.com/avian2/unidecode) *(GPL)*. It can be installed as `python-slugify[unidecode]` for those who prefer it. - How to install ==================== easy_install python-slugify |OR| easy_install python-slugify[unidecode] -- OR -- pip install python-slugify |OR| pip install python-slugify[unidecode] +Parammeters +=================== +```python +def slugify( + text, + entities=True, + decimal=True, + hexadecimal=True, + max_length=0, + word_boundary=False, + separator='-', + save_order=False, + stopwords=(), + regex_pattern=None, + lowercase=True, + replacements=() + ): + """ + Make a slug from the given text. + :param text (str): initial text + :param entities (bool): converts html entities to unicode (foo & bar -> foo-bar) + :param decimal (bool): converts html decimal to unicode (Ž -> Ž -> z) + :param hexadecimal (bool): converts html hexadecimal to unicode (Ž -> Ž -> z) + :param max_length (int): output string length + :param word_boundary (bool): truncates to end of full words (length may be shorter than max_length) + :param save_order (bool): if parameter is True and max_length > 0 return whole words in the initial order + :param separator (str): separator between words + :param stopwords (iterable): words to discount + :param regex_pattern (str): regex pattern for allowed characters + :param lowercase (bool): activate case sensitivity by setting it to False + :param replacements (iterable): list of replacement rules e.g. [['|', 'or'], ['%', 'percent']] + :return (str): slugify text + """ + {} +``` + How to use ==================== - ```python - from slugify import slugify +```python +from slugify import slugify - txt = "This is a test ---" - r = slugify(txt) - self.assertEqual(r, "this-is-a-test") +txt = "This is a test ---" +r = slugify(txt) +self.assertEqual(r, "this-is-a-test") - txt = '影師嗎' - r = slugify(txt) - self.assertEqual(r, "ying-shi-ma") +txt = '影師嗎' +r = slugify(txt) +self.assertEqual(r, "ying-shi-ma") - txt = 'C\'est déjà l\'été.' - r = slugify(txt) - self.assertEqual(r, "c-est-deja-l-ete") +txt = 'C\'est déjà l\'été.' +r = slugify(txt) +self.assertEqual(r, "c-est-deja-l-ete") - txt = 'Nín hǎo. Wǒ shì zhōng guó rén' - r = slugify(txt) - self.assertEqual(r, "nin-hao-wo-shi-zhong-guo-ren") +txt = 'Nín hǎo. Wǒ shì zhōng guó rén' +r = slugify(txt) +self.assertEqual(r, "nin-hao-wo-shi-zhong-guo-ren") - txt = 'Компьютер' - r = slugify(txt) - self.assertEqual(r, "kompiuter") +txt = 'Компьютер' +r = slugify(txt) +self.assertEqual(r, "kompiuter") - txt = 'jaja---lol-méméméoo--a' - r = slugify(txt, max_length=9) - self.assertEqual(r, "jaja-lol") +txt = 'jaja---lol-méméméoo--a' +r = slugify(txt, max_length=9) +self.assertEqual(r, "jaja-lol") - txt = 'jaja---lol-méméméoo--a' - r = slugify(txt, max_length=15, word_boundary=True) - self.assertEqual(r, "jaja-lol-a") +txt = 'jaja---lol-méméméoo--a' +r = slugify(txt, max_length=15, word_boundary=True) +self.assertEqual(r, "jaja-lol-a") - txt = 'jaja---lol-méméméoo--a' - r = slugify(txt, max_length=20, word_boundary=True, separator=".") - self.assertEqual(r, "jaja.lol.mememeoo.a") +txt = 'jaja---lol-méméméoo--a' +r = slugify(txt, max_length=20, word_boundary=True, separator=".") +self.assertEqual(r, "jaja.lol.mememeoo.a") - txt = 'one two three four five' - r = slugify(txt, max_length=13, word_boundary=True, save_order=True) - self.assertEqual(r, "one-two-three") +txt = 'one two three four five' +r = slugify(txt, max_length=13, word_boundary=True, save_order=True) +self.assertEqual(r, "one-two-three") - txt = 'the quick brown fox jumps over the lazy dog' - r = slugify(txt, stopwords=['the']) - self.assertEqual(r, 'quick-brown-fox-jumps-over-lazy-dog') +txt = 'the quick brown fox jumps over the lazy dog' +r = slugify(txt, stopwords=['the']) +self.assertEqual(r, 'quick-brown-fox-jumps-over-lazy-dog') - txt = 'the quick brown fox jumps over the lazy dog in a hurry' - r = slugify(txt, stopwords=['the', 'in', 'a', 'hurry']) - self.assertEqual(r, 'quick-brown-fox-jumps-over-lazy-dog') +txt = 'the quick brown fox jumps over the lazy dog in a hurry' +r = slugify(txt, stopwords=['the', 'in', 'a', 'hurry']) +self.assertEqual(r, 'quick-brown-fox-jumps-over-lazy-dog') - txt = 'thIs Has a stopword Stopword' - r = slugify(txt, stopwords=['Stopword'], lowercase=False) - self.assertEqual(r, 'thIs-Has-a-stopword') +txt = 'thIs Has a stopword Stopword' +r = slugify(txt, stopwords=['Stopword'], lowercase=False) +self.assertEqual(r, 'thIs-Has-a-stopword') - txt = "___This is a test___" - regex_pattern = r'[^-a-z0-9_]+' - r = slugify(txt, regex_pattern=regex_pattern) - self.assertEqual(r, "___this-is-a-test___") +txt = "___This is a test___" +regex_pattern = r'[^-a-z0-9_]+' +r = slugify(txt, regex_pattern=regex_pattern) +self.assertEqual(r, "___this-is-a-test___") - txt = "___This is a test___" - regex_pattern = r'[^-a-z0-9_]+' - r = slugify(txt, separator='_', regex_pattern=regex_pattern) - self.assertNotEqual(r, "_this_is_a_test_") +txt = "___This is a test___" +regex_pattern = r'[^-a-z0-9_]+' +r = slugify(txt, separator='_', regex_pattern=regex_pattern) +self.assertNotEqual(r, "_this_is_a_test_") - txt = '10 | 20 %' - r = slugify(txt, replacements=[['|', 'or'], ['%', 'percent']]) - self.assertEqual(r, "10-or-20-percent") +txt = '10 | 20 %' +r = slugify(txt, replacements=[['|', 'or'], ['%', 'percent']]) +self.assertEqual(r, "10-or-20-percent") - ``` +``` For more examples, have a look at the [test.py](test.py) file. @@ -137,4 +172,4 @@ X.Y.Z Version Sponsors ==================== -[![Surge](https://www.surgeforward.com/wp-content/themes/understrap-master/images/logo.png)](https://github.com/surgeforward) +[Surge](https://github.com/surgeforward) diff --git a/pycodestyle.sh b/format.sh similarity index 100% rename from pycodestyle.sh rename to format.sh diff --git a/slugify/__init__.py b/slugify/__init__.py index 0859113..c351e2e 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -3,4 +3,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '3.0.2' +__version__ = '3.0.3' diff --git a/slugify/slugify.py b/slugify/slugify.py index fcd1d22..f9bd23d 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -80,11 +80,11 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w """ Make a slug from the given text. :param text (str): initial text - :param entities (bool): - :param decimal (bool): - :param hexadecimal (bool): + :param entities (bool): converts html entities to unicode (foo & bar -> foo-bar) + :param decimal (bool): converts html decimal to unicode (Ž -> Ž -> z) + :param hexadecimal (bool): converts html hexadecimal to unicode (Ž -> Ž -> z) :param max_length (int): output string length - :param word_boundary (bool): + :param word_boundary (bool): truncates to complete word even if length ends up shorter than max_length :param save_order (bool): if parameter is True and max_length > 0 return whole words in the initial order :param separator (str): separator between words :param stopwords (iterable): words to discount diff --git a/test.py b/test.py index 78b1956..e1efe38 100644 --- a/test.py +++ b/test.py @@ -142,11 +142,36 @@ def test_stopwords_with_different_separator(self): r = slugify(txt, stopwords=['the'], separator=' ') self.assertEqual(r, 'quick brown fox jumps over lazy dog') - def test_html_entities(self): + def test_html_entities_on(self): txt = 'foo & bar' r = slugify(txt) self.assertEqual(r, 'foo-bar') + def test_html_entities_off(self): + txt = 'foo & bar' + r = slugify(txt, entities=False) + self.assertEqual(r, 'foo-amp-bar') + + def test_html_decimal_on(self): + txt = 'Ž' + r = slugify(txt, decimal=True) + self.assertEqual(r, 'z') + + def test_html_decimal_off(self): + txt = 'Ž' + r = slugify(txt, entities=False, decimal=False) + self.assertEqual(r, '381') + + def test_html_hexadecimal_on(self): + txt = 'Ž' + r = slugify(txt, hexadecimal=True) + self.assertEqual(r, 'z') + + def test_html_hexadecimal_off(self): + txt = 'Ž' + r = slugify(txt, hexadecimal=False) + self.assertEqual(r, 'x17d') + def test_starts_with_number(self): txt = '10 amazing secrets' r = slugify(txt) From 408bce4b524dfe7a27a734707979448b97b4baac Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Sat, 27 Jul 2019 22:39:43 -0400 Subject: [PATCH 025/119] pyDoc options --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 78c22d1..a8fc096 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ How to install -- OR -- pip install python-slugify |OR| pip install python-slugify[unidecode] -Parammeters +Options =================== ```python def slugify( From d56eb4c8eeb58aa571fd3aa6a14e7442bb2b26f5 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Sat, 27 Jul 2019 22:42:26 -0400 Subject: [PATCH 026/119] rename format.sh --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c821af2..5e311a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ install: - pip install https://github.com/un33k/pyflakes/tarball/master before_script: - - "bash pycodestyle.sh" + - "bash format.sh" - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pyflakes -x W slugify; fi script: coverage run --source=slugify test.py From c8110af8a1649040a4458a0078655434a8aacc95 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Sat, 27 Jul 2019 22:43:54 -0400 Subject: [PATCH 027/119] update readme --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index a8fc096..20f8c47 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,6 @@ def slugify( :param replacements (iterable): list of replacement rules e.g. [['|', 'or'], ['%', 'percent']] :return (str): slugify text """ - {} ``` How to use From b79264267ffbdc69b419b75ca5070bfe5cf4197e Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Sat, 27 Jul 2019 22:52:59 -0400 Subject: [PATCH 028/119] change log --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82ed7ba..96ed05d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.3 + - Add Options to readme + - Add more unit tests + ## 3.0.2 - Add official support of Py 3.7 From b79331ced4d93b15dc35bb0a356ccbd03604c123 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Sat, 27 Jul 2019 22:57:38 -0400 Subject: [PATCH 029/119] remove unicode chars from file --- slugify/slugify.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/slugify/slugify.py b/slugify/slugify.py index f9bd23d..6e66ace 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -80,9 +80,9 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w """ Make a slug from the given text. :param text (str): initial text - :param entities (bool): converts html entities to unicode (foo & bar -> foo-bar) - :param decimal (bool): converts html decimal to unicode (Ž -> Ž -> z) - :param hexadecimal (bool): converts html hexadecimal to unicode (Ž -> Ž -> z) + :param entities (bool): converts html entities to unicode + :param decimal (bool): converts html decimal to unicode + :param hexadecimal (bool): converts html hexadecimal to unicode :param max_length (int): output string length :param word_boundary (bool): truncates to complete word even if length ends up shorter than max_length :param save_order (bool): if parameter is True and max_length > 0 return whole words in the initial order From e7b036089a0eb1021fea74c70f34c5fae4d03885 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Sat, 27 Jul 2019 23:01:22 -0400 Subject: [PATCH 030/119] add unit test, update readme --- .travis.yml | 2 +- CHANGELOG.md | 4 ++ README.md | 138 ++++++++++++++++++++++-------------- pycodestyle.sh => format.sh | 0 slugify/__init__.py | 2 +- slugify/slugify.py | 8 +-- test.py | 27 ++++++- 7 files changed, 122 insertions(+), 59 deletions(-) rename pycodestyle.sh => format.sh (100%) diff --git a/.travis.yml b/.travis.yml index c821af2..5e311a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ install: - pip install https://github.com/un33k/pyflakes/tarball/master before_script: - - "bash pycodestyle.sh" + - "bash format.sh" - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pyflakes -x W slugify; fi script: coverage run --source=slugify test.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 82ed7ba..96ed05d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.3 + - Add Options to readme + - Add more unit tests + ## 3.0.2 - Add official support of Py 3.7 diff --git a/README.md b/README.md index 13c94a7..20f8c47 100644 --- a/README.md +++ b/README.md @@ -19,82 +19,116 @@ This module, by default installs and uses [text-unidecode](https://github.com/km However, there is an alternative decoding package called [Unidecode](https://github.com/avian2/unidecode) *(GPL)*. It can be installed as `python-slugify[unidecode]` for those who prefer it. - How to install ==================== easy_install python-slugify |OR| easy_install python-slugify[unidecode] -- OR -- pip install python-slugify |OR| pip install python-slugify[unidecode] +Options +=================== +```python +def slugify( + text, + entities=True, + decimal=True, + hexadecimal=True, + max_length=0, + word_boundary=False, + separator='-', + save_order=False, + stopwords=(), + regex_pattern=None, + lowercase=True, + replacements=() + ): + """ + Make a slug from the given text. + :param text (str): initial text + :param entities (bool): converts html entities to unicode (foo & bar -> foo-bar) + :param decimal (bool): converts html decimal to unicode (Ž -> Ž -> z) + :param hexadecimal (bool): converts html hexadecimal to unicode (Ž -> Ž -> z) + :param max_length (int): output string length + :param word_boundary (bool): truncates to end of full words (length may be shorter than max_length) + :param save_order (bool): if parameter is True and max_length > 0 return whole words in the initial order + :param separator (str): separator between words + :param stopwords (iterable): words to discount + :param regex_pattern (str): regex pattern for allowed characters + :param lowercase (bool): activate case sensitivity by setting it to False + :param replacements (iterable): list of replacement rules e.g. [['|', 'or'], ['%', 'percent']] + :return (str): slugify text + """ +``` + How to use ==================== - ```python - from slugify import slugify +```python +from slugify import slugify - txt = "This is a test ---" - r = slugify(txt) - self.assertEqual(r, "this-is-a-test") +txt = "This is a test ---" +r = slugify(txt) +self.assertEqual(r, "this-is-a-test") - txt = '影師嗎' - r = slugify(txt) - self.assertEqual(r, "ying-shi-ma") +txt = '影師嗎' +r = slugify(txt) +self.assertEqual(r, "ying-shi-ma") - txt = 'C\'est déjà l\'été.' - r = slugify(txt) - self.assertEqual(r, "c-est-deja-l-ete") +txt = 'C\'est déjà l\'été.' +r = slugify(txt) +self.assertEqual(r, "c-est-deja-l-ete") - txt = 'Nín hǎo. Wǒ shì zhōng guó rén' - r = slugify(txt) - self.assertEqual(r, "nin-hao-wo-shi-zhong-guo-ren") +txt = 'Nín hǎo. Wǒ shì zhōng guó rén' +r = slugify(txt) +self.assertEqual(r, "nin-hao-wo-shi-zhong-guo-ren") - txt = 'Компьютер' - r = slugify(txt) - self.assertEqual(r, "kompiuter") +txt = 'Компьютер' +r = slugify(txt) +self.assertEqual(r, "kompiuter") - txt = 'jaja---lol-méméméoo--a' - r = slugify(txt, max_length=9) - self.assertEqual(r, "jaja-lol") +txt = 'jaja---lol-méméméoo--a' +r = slugify(txt, max_length=9) +self.assertEqual(r, "jaja-lol") - txt = 'jaja---lol-méméméoo--a' - r = slugify(txt, max_length=15, word_boundary=True) - self.assertEqual(r, "jaja-lol-a") +txt = 'jaja---lol-méméméoo--a' +r = slugify(txt, max_length=15, word_boundary=True) +self.assertEqual(r, "jaja-lol-a") - txt = 'jaja---lol-méméméoo--a' - r = slugify(txt, max_length=20, word_boundary=True, separator=".") - self.assertEqual(r, "jaja.lol.mememeoo.a") +txt = 'jaja---lol-méméméoo--a' +r = slugify(txt, max_length=20, word_boundary=True, separator=".") +self.assertEqual(r, "jaja.lol.mememeoo.a") - txt = 'one two three four five' - r = slugify(txt, max_length=13, word_boundary=True, save_order=True) - self.assertEqual(r, "one-two-three") +txt = 'one two three four five' +r = slugify(txt, max_length=13, word_boundary=True, save_order=True) +self.assertEqual(r, "one-two-three") - txt = 'the quick brown fox jumps over the lazy dog' - r = slugify(txt, stopwords=['the']) - self.assertEqual(r, 'quick-brown-fox-jumps-over-lazy-dog') +txt = 'the quick brown fox jumps over the lazy dog' +r = slugify(txt, stopwords=['the']) +self.assertEqual(r, 'quick-brown-fox-jumps-over-lazy-dog') - txt = 'the quick brown fox jumps over the lazy dog in a hurry' - r = slugify(txt, stopwords=['the', 'in', 'a', 'hurry']) - self.assertEqual(r, 'quick-brown-fox-jumps-over-lazy-dog') +txt = 'the quick brown fox jumps over the lazy dog in a hurry' +r = slugify(txt, stopwords=['the', 'in', 'a', 'hurry']) +self.assertEqual(r, 'quick-brown-fox-jumps-over-lazy-dog') - txt = 'thIs Has a stopword Stopword' - r = slugify(txt, stopwords=['Stopword'], lowercase=False) - self.assertEqual(r, 'thIs-Has-a-stopword') +txt = 'thIs Has a stopword Stopword' +r = slugify(txt, stopwords=['Stopword'], lowercase=False) +self.assertEqual(r, 'thIs-Has-a-stopword') - txt = "___This is a test___" - regex_pattern = r'[^-a-z0-9_]+' - r = slugify(txt, regex_pattern=regex_pattern) - self.assertEqual(r, "___this-is-a-test___") +txt = "___This is a test___" +regex_pattern = r'[^-a-z0-9_]+' +r = slugify(txt, regex_pattern=regex_pattern) +self.assertEqual(r, "___this-is-a-test___") - txt = "___This is a test___" - regex_pattern = r'[^-a-z0-9_]+' - r = slugify(txt, separator='_', regex_pattern=regex_pattern) - self.assertNotEqual(r, "_this_is_a_test_") +txt = "___This is a test___" +regex_pattern = r'[^-a-z0-9_]+' +r = slugify(txt, separator='_', regex_pattern=regex_pattern) +self.assertNotEqual(r, "_this_is_a_test_") - txt = '10 | 20 %' - r = slugify(txt, replacements=[['|', 'or'], ['%', 'percent']]) - self.assertEqual(r, "10-or-20-percent") +txt = '10 | 20 %' +r = slugify(txt, replacements=[['|', 'or'], ['%', 'percent']]) +self.assertEqual(r, "10-or-20-percent") - ``` +``` For more examples, have a look at the [test.py](test.py) file. @@ -137,4 +171,4 @@ X.Y.Z Version Sponsors ==================== -[![Surge](https://www.surgeforward.com/wp-content/themes/understrap-master/images/logo.png)](https://github.com/surgeforward) +[Surge](https://github.com/surgeforward) diff --git a/pycodestyle.sh b/format.sh similarity index 100% rename from pycodestyle.sh rename to format.sh diff --git a/slugify/__init__.py b/slugify/__init__.py index 0859113..c351e2e 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -3,4 +3,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '3.0.2' +__version__ = '3.0.3' diff --git a/slugify/slugify.py b/slugify/slugify.py index fcd1d22..6e66ace 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -80,11 +80,11 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w """ Make a slug from the given text. :param text (str): initial text - :param entities (bool): - :param decimal (bool): - :param hexadecimal (bool): + :param entities (bool): converts html entities to unicode + :param decimal (bool): converts html decimal to unicode + :param hexadecimal (bool): converts html hexadecimal to unicode :param max_length (int): output string length - :param word_boundary (bool): + :param word_boundary (bool): truncates to complete word even if length ends up shorter than max_length :param save_order (bool): if parameter is True and max_length > 0 return whole words in the initial order :param separator (str): separator between words :param stopwords (iterable): words to discount diff --git a/test.py b/test.py index 78b1956..e1efe38 100644 --- a/test.py +++ b/test.py @@ -142,11 +142,36 @@ def test_stopwords_with_different_separator(self): r = slugify(txt, stopwords=['the'], separator=' ') self.assertEqual(r, 'quick brown fox jumps over lazy dog') - def test_html_entities(self): + def test_html_entities_on(self): txt = 'foo & bar' r = slugify(txt) self.assertEqual(r, 'foo-bar') + def test_html_entities_off(self): + txt = 'foo & bar' + r = slugify(txt, entities=False) + self.assertEqual(r, 'foo-amp-bar') + + def test_html_decimal_on(self): + txt = 'Ž' + r = slugify(txt, decimal=True) + self.assertEqual(r, 'z') + + def test_html_decimal_off(self): + txt = 'Ž' + r = slugify(txt, entities=False, decimal=False) + self.assertEqual(r, '381') + + def test_html_hexadecimal_on(self): + txt = 'Ž' + r = slugify(txt, hexadecimal=True) + self.assertEqual(r, 'z') + + def test_html_hexadecimal_off(self): + txt = 'Ž' + r = slugify(txt, hexadecimal=False) + self.assertEqual(r, 'x17d') + def test_starts_with_number(self): txt = '10 amazing secrets' r = slugify(txt) From 413ac35c0859bf9c6e1024c993c77cb980f9645e Mon Sep 17 00:00:00 2001 From: kf <14309762+koolfunky@users.noreply.github.com> Date: Thu, 19 Sep 2019 17:12:07 +0200 Subject: [PATCH 031/119] BF(dependencies)| Bump `text_unidecode` version (#83) 1.2 -> 1.3 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4800173..9f49764 100755 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ author = 'Val Neekman' author_email = 'info@neekware.com' license = 'MIT' -install_requires = ['text-unidecode==1.2'] +install_requires = ['text-unidecode==1.3'] extras_require = {'unidecode': ['Unidecode==1.0.23']} classifiers = [ From 67c16a409063578d38bf69ee345254e67b237206 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Fri, 20 Sep 2019 12:41:06 -0400 Subject: [PATCH 032/119] upgrade to consume the latest version of dependencies --- CHANGELOG.md | 6 +++--- setup.py | 4 ++-- slugify/__init__.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96ed05d..cc98d31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ -## 3.0.3 - - Add Options to readme - - Add more unit tests +## 3.0.4 + - Now supporting text-unidecode>=1.3 + - Now supporting Unidecode>=1.1.1 ## 3.0.2 - Add official support of Py 3.7 diff --git a/setup.py b/setup.py index 9f49764..83fcf63 100755 --- a/setup.py +++ b/setup.py @@ -14,8 +14,8 @@ author = 'Val Neekman' author_email = 'info@neekware.com' license = 'MIT' -install_requires = ['text-unidecode==1.3'] -extras_require = {'unidecode': ['Unidecode==1.0.23']} +install_requires = ['text-unidecode>=1.3'] +extras_require = {'unidecode': ['Unidecode>=1.1.1']} classifiers = [ 'Development Status :: 5 - Production/Stable', diff --git a/slugify/__init__.py b/slugify/__init__.py index c351e2e..52a07b4 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -3,4 +3,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '3.0.3' +__version__ = '3.0.4' From 33bf07efc82c00b811df02bca8c5862f95db3294 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Thu, 10 Oct 2019 18:12:07 -0400 Subject: [PATCH 033/119] add special pre translation file, more unit test, updated readme --- CHANGELOG.md | 7 +++++++ README.md | 4 ++++ slugify/__init__.py | 3 ++- slugify/special.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ test.py | 5 +++++ 5 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 slugify/special.py diff --git a/CHANGELOG.md b/CHANGELOG.md index cc98d31..0e1a514 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,14 @@ +## 3.0.5 + - Add test for pre-translation (e.g German Umlaut) + - Add special char supports (optional Use) + ## 3.0.4 - Now supporting text-unidecode>=1.3 - Now supporting Unidecode>=1.1.1 +## 3.0.3 + - Remove unicode chars from file + ## 3.0.2 - Add official support of Py 3.7 diff --git a/README.md b/README.md index 20f8c47..800b7a6 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,10 @@ txt = '10 | 20 %' r = slugify(txt, replacements=[['|', 'or'], ['%', 'percent']]) self.assertEqual(r, "10-or-20-percent") +txt = 'ÜBER Über German Umlaut' +r = slugify(txt, replacements=[['Ü', 'UE'], ['ü', 'ue']]) +self.assertEqual(r, "ueber-ueber-german-umlaut") + ``` For more examples, have a look at the [test.py](test.py) file. diff --git a/slugify/__init__.py b/slugify/__init__.py index 52a07b4..0c86a4c 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -1,6 +1,7 @@ +from .special import * from .slugify import * __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '3.0.4' +__version__ = '3.0.5' diff --git a/slugify/special.py b/slugify/special.py new file mode 100644 index 0000000..767541a --- /dev/null +++ b/slugify/special.py @@ -0,0 +1,44 @@ +def add_uppercase_char(char_list): + """ Given a replacement char list, this adds uppercase chars to the list """ + + for item in char_list: + char, xlate = item + upper_dict = char.upper(), xlate.capitalize() + if upper_dict not in char_list and char != upper_dict[0]: + char_list.insert(0, upper_dict) + return char_list + + +# Language specific pre translations +# Source awesome-slugify + +_CYRILLIC = [ # package defaults: + (u'ё', u'e'), # io / yo + (u'я', u'ya'), # ia + (u'х', u'h'), # kh + (u'у', u'y'), # u + (u'щ', u'sch'), # shch + (u'ю', u'u'), # iu / yu +] +CYRILLIC = add_uppercase_char(_CYRILLIC) + +_GERMAN = [ # package defaults: + (u'ä', u'ae'), # a + (u'ö', u'oe'), # o + (u'ü', u'ue'), # u +] +GERMAN = add_uppercase_char(_GERMAN) + +_GREEK = [ # package defaults: + (u'χ', u'ch'), # kh + (u'Ξ', u'X'), # Ks + (u'ϒ', u'Y'), # U + (u'υ', u'y'), # u + (u'ύ', u'y'), + (u'ϋ', u'y'), + (u'ΰ', u'y'), +] +GREEK = add_uppercase_char(_GREEK) + +# Pre translations +PRE_TRANSLATIONS = CYRILLIC + GERMAN + GREEK diff --git a/test.py b/test.py index e1efe38..29f0ac7 100644 --- a/test.py +++ b/test.py @@ -223,6 +223,11 @@ def test_replacements(self): r = slugify(txt, replacements=[['♥', 'amour'], ['🦄', 'licorne']]) self.assertEqual(r, "i-amour-licorne") + def test_replacements_german_umlaut_custom(self): + txt = 'ÜBER Über German Umlaut' + r = slugify(txt, replacements=[['Ü', 'UE'], ['ü', 'ue']]) + self.assertEqual(r, "ueber-ueber-german-umlaut") + class TestUtils(unittest.TestCase): From b9db1bc87e3b19c5c63ab7a00b19685b7996de5d Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Thu, 10 Oct 2019 18:23:49 -0400 Subject: [PATCH 034/119] fix missing encoding in file --- CHANGELOG.md | 3 +++ slugify/__init__.py | 2 +- slugify/special.py | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e1a514..7210471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 3.0.6 + - Fixed encoding in special.py + ## 3.0.5 - Add test for pre-translation (e.g German Umlaut) - Add special char supports (optional Use) diff --git a/slugify/__init__.py b/slugify/__init__.py index 0c86a4c..b1dfecb 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -4,4 +4,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '3.0.5' +__version__ = '3.0.6' diff --git a/slugify/special.py b/slugify/special.py index 767541a..d3478d5 100644 --- a/slugify/special.py +++ b/slugify/special.py @@ -1,3 +1,6 @@ +# -*- coding: utf-8 -*- + + def add_uppercase_char(char_list): """ Given a replacement char list, this adds uppercase chars to the list """ From 588372d9e68979c2990873e9562d203ce3098442 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 15 Oct 2019 00:11:14 +0300 Subject: [PATCH 035/119] Drop support for EOL Python 2.6 and 3.2-2.4 (#87) * Drop support for EOL Python 2.6 and 3.2-3.4 * Remove duplicate class * Upgrade Python syntax with pyupgrade * Upgrade PyPy and PyPy3 and test 3.8 beta --- .travis.yml | 6 +++--- setup.py | 6 ++---- slugify/slugify.py | 4 ++-- test.py | 13 ------------- 4 files changed, 7 insertions(+), 22 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5e311a5..a393a66 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,12 @@ dist: xenial python: - "2.7" - - "3.4" - "3.5" - "3.6" - "3.7" - - "pypy2.7-6.0" - - "pypy3.5-6.0" + - "3.8-dev" + - "pypy" + - "pypy3" install: - pip install pip -U diff --git a/setup.py b/setup.py index 83fcf63..00a7f4b 100755 --- a/setup.py +++ b/setup.py @@ -24,12 +24,9 @@ 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', @@ -68,6 +65,7 @@ def get_version(package): packages=find_packages(exclude=EXCLUDE_FROM_PACKAGES), install_requires=install_requires, extras_require=extras_require, + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=classifiers, entry_points={'console_scripts': ['slugify=slugify.slugify:main']}, ) diff --git a/slugify/slugify.py b/slugify/slugify.py index 6e66ace..4268fd1 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -62,9 +62,9 @@ def smart_truncate(string, max_length=0, word_boundary=False, separator=' ', sav if word: next_len = len(truncated) + len(word) if next_len < max_length: - truncated += '{0}{1}'.format(word, separator) + truncated += '{}{}'.format(word, separator) elif next_len == max_length: - truncated += '{0}'.format(word) + truncated += '{}'.format(word) break else: if save_order: diff --git a/test.py b/test.py index 29f0ac7..98debff 100644 --- a/test.py +++ b/test.py @@ -242,18 +242,5 @@ def test_smart_truncate_no_seperator(self): self.assertEqual(r, txt) -class TestUtils(unittest.TestCase): - - def test_smart_truncate_no_max_length(self): - txt = '1,000 reasons you are #1' - r = smart_truncate(txt) - self.assertEqual(r, txt) - - def test_smart_truncate_no_seperator(self): - txt = '1,000 reasons you are #1' - r = smart_truncate(txt, max_length=100, separator='_') - self.assertEqual(r, txt) - - if __name__ == '__main__': unittest.main() From 26f96d78ddd9686c15f685d407dae42a757eec35 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Sun, 20 Oct 2019 13:29:58 -0400 Subject: [PATCH 036/119] up version --- CHANGELOG.md | 3 +++ slugify/__init__.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7210471..ba9b28c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 4.0.0 + - Drop support from 2.6, & < 3.4.5 + ## 3.0.6 - Fixed encoding in special.py diff --git a/slugify/__init__.py b/slugify/__init__.py index b1dfecb..ab72a8e 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -4,4 +4,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '3.0.6' +__version__ = '4.0.0' From 76f327216bc52adc6115eb6d1483405958810c5c Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Sun, 20 Oct 2019 13:39:51 -0400 Subject: [PATCH 037/119] Drop support for old python - cleanup - up version (#88) * Drop support for EOL Python 2.6 and 3.2-2.4 (#87) * Drop support for EOL Python 2.6 and 3.2-3.4 * Remove duplicate class * Upgrade Python syntax with pyupgrade * Upgrade PyPy and PyPy3 and test 3.8 beta * up version --- .travis.yml | 6 +++--- CHANGELOG.md | 3 +++ setup.py | 6 ++---- slugify/__init__.py | 2 +- slugify/slugify.py | 4 ++-- test.py | 13 ------------- 6 files changed, 11 insertions(+), 23 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5e311a5..a393a66 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,12 @@ dist: xenial python: - "2.7" - - "3.4" - "3.5" - "3.6" - "3.7" - - "pypy2.7-6.0" - - "pypy3.5-6.0" + - "3.8-dev" + - "pypy" + - "pypy3" install: - pip install pip -U diff --git a/CHANGELOG.md b/CHANGELOG.md index 7210471..ba9b28c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 4.0.0 + - Drop support from 2.6, & < 3.4.5 + ## 3.0.6 - Fixed encoding in special.py diff --git a/setup.py b/setup.py index 83fcf63..00a7f4b 100755 --- a/setup.py +++ b/setup.py @@ -24,12 +24,9 @@ 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', @@ -68,6 +65,7 @@ def get_version(package): packages=find_packages(exclude=EXCLUDE_FROM_PACKAGES), install_requires=install_requires, extras_require=extras_require, + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=classifiers, entry_points={'console_scripts': ['slugify=slugify.slugify:main']}, ) diff --git a/slugify/__init__.py b/slugify/__init__.py index b1dfecb..ab72a8e 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -4,4 +4,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '3.0.6' +__version__ = '4.0.0' diff --git a/slugify/slugify.py b/slugify/slugify.py index 6e66ace..4268fd1 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -62,9 +62,9 @@ def smart_truncate(string, max_length=0, word_boundary=False, separator=' ', sav if word: next_len = len(truncated) + len(word) if next_len < max_length: - truncated += '{0}{1}'.format(word, separator) + truncated += '{}{}'.format(word, separator) elif next_len == max_length: - truncated += '{0}'.format(word) + truncated += '{}'.format(word) break else: if save_order: diff --git a/test.py b/test.py index 29f0ac7..98debff 100644 --- a/test.py +++ b/test.py @@ -242,18 +242,5 @@ def test_smart_truncate_no_seperator(self): self.assertEqual(r, txt) -class TestUtils(unittest.TestCase): - - def test_smart_truncate_no_max_length(self): - txt = '1,000 reasons you are #1' - r = smart_truncate(txt) - self.assertEqual(r, txt) - - def test_smart_truncate_no_seperator(self): - txt = '1,000 reasons you are #1' - r = smart_truncate(txt, max_length=100, separator='_') - self.assertEqual(r, txt) - - if __name__ == '__main__': unittest.main() From 2889262308f3fba2b50d8ff7ce404a09353a9568 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Sun, 20 Oct 2019 13:45:47 -0400 Subject: [PATCH 038/119] add contribution section --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 800b7a6..239f263 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,12 @@ To run the tests against the current environment: python test.py +Contribution +==================== + +Please read the ([wiki](https://github.com/un33k/python-slugify/wiki/Python-Slugify-Wiki)) page prior to raising any PRs. + + License ==================== From e785874f0ea053cea393d396b615235e7c0ad179 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 20 Oct 2019 20:49:35 +0300 Subject: [PATCH 039/119] Use SVG badge for consistency --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 239f263..d3f2a7c 100644 --- a/README.md +++ b/README.md @@ -165,8 +165,8 @@ X.Y.Z Version `MINOR` version -- when you add functionality in a backwards-compatible manner, and `PATCH` version -- when you make backwards-compatible bug fixes. -[status-image]: https://secure.travis-ci.org/un33k/python-slugify.png?branch=master -[status-link]: http://travis-ci.org/un33k/python-slugify?branch=master +[status-image]: https://travis-ci.org/un33k/python-slugify.svg?branch=master +[status-link]: https://travis-ci.org/un33k/python-slugify [version-image]: https://img.shields.io/pypi/v/python-slugify.svg [version-link]: https://pypi.python.org/pypi/python-slugify From 3b51e23c81f466f3bd7dd916407d074ed0f802f5 Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Sat, 14 Mar 2020 02:14:52 +0000 Subject: [PATCH 040/119] Create command line tool that can set all parameters --- setup.py | 2 +- slugify/__main__.py | 93 ++++++++++++++++++++++++++++++++++++ slugify/slugify.py | 8 ---- test.py | 112 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 205 insertions(+), 10 deletions(-) create mode 100644 slugify/__main__.py diff --git a/setup.py b/setup.py index 00a7f4b..b14d93f 100755 --- a/setup.py +++ b/setup.py @@ -67,5 +67,5 @@ def get_version(package): extras_require=extras_require, python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=classifiers, - entry_points={'console_scripts': ['slugify=slugify.slugify:main']}, + entry_points={'console_scripts': ['slugify=slugify.__main__:entrypoint']}, ) diff --git a/slugify/__main__.py b/slugify/__main__.py new file mode 100644 index 0000000..a11989b --- /dev/null +++ b/slugify/__main__.py @@ -0,0 +1,93 @@ +from __future__ import print_function, absolute_import +import argparse +import sys + +from .slugify import slugify, DEFAULT_SEPARATOR + + +def parse_args(argv): + parser = argparse.ArgumentParser(description="Sluggify string") + + input_group = parser.add_argument_group(description="Input") + input_group.add_argument("input_string", nargs='*', + help='Text to slugify') + input_group.add_argument("--stdin", action='store_true', + help="Take the text from STDIN") + + parser.add_argument("--no-entities", action='store_false', dest='entities', default=True, + help="Do not convert HTML entities to unicode") + parser.add_argument("--no-decimal", action='store_false', dest='decimal', default=True, + help="Do not convert HTML decimal to unicode") + parser.add_argument("--no-hexadecimal", action='store_false', dest='hexadecimal', default=True, + help="Do not convert HTML hexadecimal to unicode") + parser.add_argument("--max-length", type=int, default=0, + help="Output string length, 0 for no limit") + parser.add_argument("--word-boundary", action='store_true', default=False, + help="Truncate to complete word even if length ends up shorter than --max_length") + parser.add_argument("--save-order", action='store_true', default=False, + help="When set and --max_length > 0 return whole words in the initial order") + parser.add_argument("--separator", type=str, default=DEFAULT_SEPARATOR, + help="Separator between words. By default " + DEFAULT_SEPARATOR) + parser.add_argument("--stopwords", nargs='+', + help="Words to discount") + parser.add_argument("--regex-pattern", + help="Python regex pattern for allowed characters") + parser.add_argument("--no-lowercase", action='store_false', dest='lowercase', default=True, + help="Activate case sensitivity") + parser.add_argument("--replacements", nargs='+', + help="""Additional replacement rules e.g. "|->or", "%%->percent".""") + + args = parser.parse_args(argv[1:]) + + if args.input_string and args.stdin: + parser.error("Input strings and --stdin cannot work together") + + if args.replacements: + def split_check(repl): + SEP = '->' + if SEP not in repl: + parser.error("Replacements must be of the form: ORIGINAL{SEP}REPLACED".format(SEP=SEP)) + return repl.split(SEP, 1) + args.replacements = [split_check(repl) for repl in args.replacements] + + if args.input_string: + args.input_string = " ".join(args.input_string) + elif args.stdin: + args.input_string = sys.stdin.read() + + if not args.input_string: + args.input_string = '' + + return args + + +def slugify_params(args): + return dict( + text=args.input_string, + entities=args.entities, + decimal=args.decimal, + hexadecimal=args.hexadecimal, + max_length=args.max_length, + word_boundary=args.word_boundary, + save_order=args.save_order, + separator=args.separator, + stopwords=args.stopwords, + lowercase=args.lowercase, + replacements=args.replacements + ) + + +def main(argv=None): # pragma: no cover + """ Run this program """ + if argv is None: + argv = sys.argv + args = parse_args(argv) + params = slugify_params(args) + try: + print(slugify(**params)) + except KeyboardInterrupt: + sys.exit(-1) + + +if __name__ == '__main__': # pragma: no cover + main() diff --git a/slugify/slugify.py b/slugify/slugify.py index 4268fd1..bb3aa95 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -178,11 +178,3 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w text = text.replace(DEFAULT_SEPARATOR, separator) return text - - -def main(): # pragma: no cover - if len(sys.argv) < 2: - print("Usage %s TEXT TO SLUGIFY" % sys.argv[0]) - else: - text = ' '.join(sys.argv[1:]) - print(slugify(text)) diff --git a/test.py b/test.py index 98debff..2433256 100644 --- a/test.py +++ b/test.py @@ -1,8 +1,13 @@ # -*- coding: utf-8 -*- - +import io +import os +import sys import unittest +from contextlib import contextmanager + from slugify import slugify from slugify import smart_truncate +from slugify.__main__ import slugify_params, parse_args class TestSlugification(unittest.TestCase): @@ -242,5 +247,110 @@ def test_smart_truncate_no_seperator(self): self.assertEqual(r, txt) +PY3 = sys.version_info.major == 3 + + +@contextmanager +def captured_stderr(): + backup = sys.stderr + sys.stderr = io.StringIO() if PY3 else io.BytesIO() + try: + yield sys.stderr + finally: + sys.stderr = backup + + +@contextmanager +def loaded_stdin(contents): + backup = sys.stdin + sys.stdin = io.StringIO(contents) if PY3 else io.BytesIO(contents) + try: + yield sys.stdin + finally: + sys.stdin = backup + + +class TestCommandParams(unittest.TestCase): + DEFAULTS = { + 'entities': True, + 'decimal': True, + 'hexadecimal': True, + 'max_length': 0, + 'word_boundary': False, + 'save_order': False, + 'separator': '-', + 'stopwords': None, + 'lowercase': True, + 'replacements': None + } + + def get_params_from_cli(self, *argv): + args = parse_args([None] + list(argv)) + return slugify_params(args) + + def make_params(self, **values): + return dict(self.DEFAULTS, **values) + + def assertParamsMatch(self, expected, checked): + reduced_checked = {} + for key in expected.keys(): + reduced_checked[key] = checked[key] + self.assertEqual(expected, reduced_checked) + + def test_defaults(self): + params = self.get_params_from_cli() + self.assertParamsMatch(self.DEFAULTS, params) + + def test_negative_flags(self): + params = self.get_params_from_cli('--no-entities', '--no-decimal', '--no-hexadecimal', '--no-lowercase') + expected = self.make_params(entities=False, decimal=False, hexadecimal=False, lowercase=False) + self.assertFalse(expected['lowercase']) + self.assertFalse(expected['word_boundary']) + self.assertParamsMatch(expected, params) + + def test_affirmative_flags(self): + params = self.get_params_from_cli('--word-boundary', '--save-order') + expected = self.make_params(word_boundary=True, save_order=True) + self.assertParamsMatch(expected, params) + + def test_valued_arguments(self): + params = self.get_params_from_cli('--stopwords', 'abba', 'beatles', '--max-length', '98', '--separator', '+') + expected = self.make_params(stopwords=['abba', 'beatles'], max_length=98, separator='+') + self.assertParamsMatch(expected, params) + + def test_replacements_right(self): + params = self.get_params_from_cli('--replacements', 'A->B', 'C->D') + expected = self.make_params(replacements=[['A', 'B'], ['C', 'D']]) + self.assertParamsMatch(expected, params) + + def test_replacements_wrong(self): + with self.assertRaises(SystemExit) as err, captured_stderr() as cse: + self.get_params_from_cli('--replacements', 'A--B') + self.assertEqual(err.exception.code, 2) + self.assertIn("Replacements must be of the form: ORIGINAL->REPLACED", cse.getvalue()) + + def test_text_in_cli(self): + params = self.get_params_from_cli('Cool Text') + expected = self.make_params(text='Cool Text') + self.assertParamsMatch(expected, params) + + def test_text_in_cli_multi(self): + params = self.get_params_from_cli('Cool', 'Text') + expected = self.make_params(text='Cool Text') + self.assertParamsMatch(expected, params) + + def test_text_in_stdin(self): + with loaded_stdin("Cool Stdin"): + params = self.get_params_from_cli('--stdin') + expected = self.make_params(text='Cool Stdin') + self.assertParamsMatch(expected, params) + + def test_two_text_sources_fails(self): + with self.assertRaises(SystemExit) as err, captured_stderr() as cse: + self.get_params_from_cli('--stdin', 'Text') + self.assertEqual(err.exception.code, 2) + self.assertIn("Input strings and --stdin cannot work together", cse.getvalue()) + + if __name__ == '__main__': unittest.main() From 866551b09cc0de12fde216bf836a289cc397e7c0 Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Sun, 15 Mar 2020 14:59:27 +0000 Subject: [PATCH 041/119] Use main as entrypoint --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b14d93f..506f77b 100755 --- a/setup.py +++ b/setup.py @@ -67,5 +67,5 @@ def get_version(package): extras_require=extras_require, python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=classifiers, - entry_points={'console_scripts': ['slugify=slugify.__main__:entrypoint']}, + entry_points={'console_scripts': ['slugify=slugify.__main__:main']}, ) From 9c55ecc51447ce4d742d0d3fdf7a165ef99339a7 Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Sun, 15 Mar 2020 15:03:05 +0000 Subject: [PATCH 042/119] Add test for multivalued option with text in command --- test.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test.py b/test.py index 2433256..ddf1bf4 100644 --- a/test.py +++ b/test.py @@ -351,6 +351,13 @@ def test_two_text_sources_fails(self): self.assertEqual(err.exception.code, 2) self.assertIn("Input strings and --stdin cannot work together", cse.getvalue()) + def test_multivalued_options_with_text(self): + text = "the quick brown fox jumps over the lazy dog in a hurry" + cli_args = "--stopwords the in a hurry -- {}".format(text).split() + params = self.get_params_from_cli(*cli_args) + self.assertEqual(params['text'], text) + self.assertEqual(params['stopwords'], ['the', 'in', 'a', 'hurry']) + if __name__ == '__main__': unittest.main() From d1599a799d551d6bf57b1a727af10c125fc6a257 Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Tue, 7 Apr 2020 23:32:03 +0100 Subject: [PATCH 043/119] Add details about the command line tool to README --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index d3f2a7c..510a4b6 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,32 @@ self.assertEqual(r, "ueber-ueber-german-umlaut") For more examples, have a look at the [test.py](test.py) file. +Command Line Options +==================== + +With the package, a command line tool called `slugify` is also installed. + +It allows convenient command line access to all the features the `slugify` function supports. Call it with `-h` for help. + +The command can take its input directly on the command line or from STDIN (when the `--stdin` flag is passed): + +``` +$ echo "Taking input from STDIN" | slugify --stdin +taking-input-from-stdin +``` +``` +$ slugify taking input from the command line +taking-input-from-the-command-line +``` + +Please note that when a multi-valued option such as `--stopwords` or `--replacements` is passed, you need to use `--` as separator before you start with the input: + +``` +$ slugify --stopwords the in a hurry -- the quick brown fox jumps over the lazy dog in a hurry +quick-brown-fox-jumps-over-lazy-dog +``` + + Running the tests ==================== From b47c4997b943f90af7a1955880076461a15b1adc Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Mon, 29 Jun 2020 23:11:14 -0400 Subject: [PATCH 044/119] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 510a4b6..a73b39f 100644 --- a/README.md +++ b/README.md @@ -207,4 +207,4 @@ X.Y.Z Version Sponsors ==================== -[Surge](https://github.com/surgeforward) +[Neekware Inc.](neekware.com) From c6f14251c16b4d1d2d3ec7ebb18d31a63b83e91f Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Mon, 29 Jun 2020 23:11:44 -0400 Subject: [PATCH 045/119] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a73b39f..c5803f1 100644 --- a/README.md +++ b/README.md @@ -207,4 +207,4 @@ X.Y.Z Version Sponsors ==================== -[Neekware Inc.](neekware.com) +[Neekware Inc.](http://neekware.com) From f68a2ab8de0fed45777feeed0b3c07e34a0ffb6e Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Mon, 29 Jun 2020 23:17:19 -0400 Subject: [PATCH 046/119] enable python 3.8 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a393a66..ee9335c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ python: - "3.5" - "3.6" - "3.7" - - "3.8-dev" + - "3.8" - "pypy" - "pypy3" From c340834bb2afb24d78151c018496f4273fb7be37 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Mon, 29 Jun 2020 23:22:04 -0400 Subject: [PATCH 047/119] up version --- CHANGELOG.md | 3 +++ slugify/__init__.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba9b28c..a3bf922 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 4.0.1 + - Add support for Py 3.8 + ## 4.0.0 - Drop support from 2.6, & < 3.4.5 diff --git a/slugify/__init__.py b/slugify/__init__.py index ab72a8e..d69e2ed 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -4,4 +4,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '4.0.0' +__version__ = '4.0.1' From 3cc1069d37431efd366a0894067aa4c3293dca3e Mon Sep 17 00:00:00 2001 From: Jon Betts Date: Fri, 30 Apr 2021 18:57:48 +0100 Subject: [PATCH 048/119] Add a tox file to automatically run the tests and add Python 3.9 --- .python-version | 8 ++++++++ .travis.yml | 4 +++- README.md | 4 ++++ setup.py | 2 ++ tox.ini | 18 ++++++++++++++++++ 5 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 .python-version create mode 100644 tox.ini diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..0d88666 --- /dev/null +++ b/.python-version @@ -0,0 +1,8 @@ +3.9.2 +3.8.8 +3.7.10 +3.6.13 +3.5.10 +2.7.18 +pypy-5.7.1 +pypy3.7-7.3.3 diff --git a/.travis.yml b/.travis.yml index ee9335c..3c917db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ python: - "3.6" - "3.7" - "3.8" + - "3.9" - "pypy" - "pypy3" @@ -14,7 +15,8 @@ install: - pip install pip -U - pip install -e . - pip install pycodestyle - - pip install coveralls + # Avoid cryptography compilation problems with pypy and 2.7 + - pip install coveralls 'cryptography<=3.0;python_version=="2.7"' - pip install https://github.com/un33k/pyflakes/tarball/master before_script: diff --git a/README.md b/README.md index c5803f1..0e0195f 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,10 @@ quick-brown-fox-jumps-over-lazy-dog Running the tests ==================== +To run the tests against all environments: + + tox + To run the tests against the current environment: python test.py diff --git a/setup.py b/setup.py index 506f77b..038a85f 100755 --- a/setup.py +++ b/setup.py @@ -30,6 +30,8 @@ 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', ] diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..d189cbd --- /dev/null +++ b/tox.ini @@ -0,0 +1,18 @@ +[tox] +envlist = py{39,38,37,36,35,27},pypy,pypy3 + +[testenv] +deps= + -e . +commands = + python -m unittest test + +[testenv:format] +deps = pycodestyle +allowlist_externals = sh +commands = sh format.sh + +[testenv:coverage] +deps = coverage +commands = + coverage run --source=slugify test.py \ No newline at end of file From 319559cb43f239ac0d3bc7b975a78059bf4a1086 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Fri, 30 Apr 2021 15:33:34 -0400 Subject: [PATCH 049/119] Add support for 3.9, drop support for 2.7,3.5 --- .python-version | 3 - .travis.yml | 11 +--- CHANGELOG.md | 148 +++++++++++++++++++++++-------------------- README.md | 60 +++++++----------- dev.requirements.txt | 2 +- setup.py | 5 +- slugify/__init__.py | 2 +- slugify/__main__.py | 2 +- tox.ini | 2 +- 9 files changed, 109 insertions(+), 126 deletions(-) diff --git a/.python-version b/.python-version index 0d88666..a8733ab 100644 --- a/.python-version +++ b/.python-version @@ -2,7 +2,4 @@ 3.8.8 3.7.10 3.6.13 -3.5.10 -2.7.18 -pypy-5.7.1 pypy3.7-7.3.3 diff --git a/.travis.yml b/.travis.yml index 3c917db..2f8b3ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,28 +2,21 @@ language: python dist: xenial python: - - "2.7" - - "3.5" - "3.6" - "3.7" - "3.8" - "3.9" - - "pypy" - "pypy3" install: - pip install pip -U - pip install -e . - pip install pycodestyle - # Avoid cryptography compilation problems with pypy and 2.7 - - pip install coveralls 'cryptography<=3.0;python_version=="2.7"' - - pip install https://github.com/un33k/pyflakes/tarball/master + - pip install coveralls before_script: - "bash format.sh" - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pyflakes -x W slugify; fi script: coverage run --source=slugify test.py -after_success: - coveralls +after_success: coveralls diff --git a/CHANGELOG.md b/CHANGELOG.md index a3bf922..82ae711 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,186 +1,194 @@ +## 5.0.0 + +- Add support for Py 3.9 - added tox (@jon-betts - Thx) +- Drop support for python 2.7, 3.5 & friends + ## 4.0.1 - - Add support for Py 3.8 + +- Add support for Py 3.8 +- Last version with `official` python 2.7 and <= 3.5 support ## 4.0.0 - - Drop support from 2.6, & < 3.4.5 + +- Drop support from 2.6, & < 3.4.5 ## 3.0.6 - - Fixed encoding in special.py + +- Fixed encoding in special.py ## 3.0.5 - - Add test for pre-translation (e.g German Umlaut) - - Add special char supports (optional Use) + +- Add test for pre-translation (e.g German Umlaut) +- Add special char supports (optional Use) ## 3.0.4 - - Now supporting text-unidecode>=1.3 - - Now supporting Unidecode>=1.1.1 + +- Now supporting text-unidecode>=1.3 +- Now supporting Unidecode>=1.1.1 ## 3.0.3 - - Remove unicode chars from file + +- Remove unicode chars from file ## 3.0.2 - - Add official support of Py 3.7 + +- Add official support of Py 3.7 ## 3.0.1 - - Add test.py to manifest + +- Add test.py to manifest ## 3.0.0 - - Upgrade Unidecode - - Promote text-unidecode as the primary decoding package - - Add Unidecode as an optional extra. "pip install python-slugify[unidecode]" + +- Upgrade Unidecode +- Promote text-unidecode as the primary decoding package +- Add Unidecode as an optional extra. "pip install python-slugify[unidecode]" ## 2.0.1 - - Add replacements option e.g. [['|', 'or'], ['%', 'percent'], ['-', '_']] (@andriyor) + +- Add replacements option e.g. [['|', 'or'], ['%', 'percent'], ['-', '_']] (@andriyor) ## 2.0.0 - - Fix alternative dependency installation + +- Fix alternative dependency installation ## 1.2.6 - - Add support for case sensitive slugs (@s-m-e) + +- Add support for case sensitive slugs (@s-m-e) ## 1.2.5 - - Add support for using text-unidecode (@bolkedebruin) - - Switch to pycodestyle instead of pep8 + +- Add support for using text-unidecode (@bolkedebruin) +- Switch to pycodestyle instead of pep8 ## 1.2.4 - - Remove build artifacts during packaging - - Simplify the setup.py file (@reece) + +- Remove build artifacts during packaging +- Simplify the setup.py file (@reece) ## 1.2.3 - - Republish - possible corrupt 1.2.2 build + +- Republish - possible corrupt 1.2.2 build ## 1.2.2 - - Add `regex_pattern` option. (@vrbaskiz) - - Add Python 3.6 support + +- Add `regex_pattern` option. (@vrbaskiz) +- Add Python 3.6 support ## 1.2.1 - - Including certain files (e.g. license.md) in sdists via MANIFEST.in (@proinsias) - - Relax licensing by moving from BSD to MIT - - Add Python 3.5 support - - Add more tests + +- Including certain files (e.g. license.md) in sdists via MANIFEST.in (@proinsias) +- Relax licensing by moving from BSD to MIT +- Add Python 3.5 support +- Add more tests ## 1.2.0 Backward incompatible change: (@fabiocaccamo) - - In version < 1.2.0 all single quotes ( ' ) were removed, and - moving forward, >= 1.2.0, they will be replaced with ( - ). - Example: - < 1.2.0 -- ('C\'est déjà l\'été.' -> "cest-deja-lete") - >= 1.2.0 -- ('C\'est déjà l\'été.' -> "c-est-deja-l-ete") +- In version < 1.2.0 all single quotes ( ' ) were removed, and + moving forward, >= 1.2.0, they will be replaced with ( - ). + Example: + < 1.2.0 -- ('C\'est déjà l\'été.' -> "cest-deja-lete") + > = 1.2.0 -- ('C\'est déjà l\'été.' -> "c-est-deja-l-ete") ## 1.1.4 Bugfix: - - Add more test cases, dropped `official` support for python 3.2 - +- Add more test cases, dropped `official` support for python 3.2 ## 1.1.3 Bugfix: - - Handle unichar in python 3.x - +- Handle unichar in python 3.x ## 1.1.2 Enhancement: - - Ability to remove `stopwords` from string - +- Ability to remove `stopwords` from string ## 1.0.2 Enhancement: - - A new PyPI release - +- A new PyPI release ## 1.0.1 Enhancement: - - Promoting to production grade - +- Promoting to production grade ## 0.1.1 Enhancement: - - Added option to save word order - - Removed 2to3 dependency - - Added more tests - +- Added option to save word order +- Removed 2to3 dependency +- Added more tests ## 0.1.0 Enhancement: - - Added more test - - Added test for python 3.4 - +- Added more test +- Added test for python 3.4 ## 0.0.9 Enhancement: - - Enable console_scripts - +- Enable console_scripts ## 0.0.8 Enhancement: - - Move logic out of __init__.py - - Added console_scripts (@ekamil) - - Updated pep8.sh - - Added pypy support - +- Move logic out of **init**.py +- Added console_scripts (@ekamil) +- Updated pep8.sh +- Added pypy support ## 0.0.7 Enhancement: - - Handle encoding in setup file - - Update ReadME, ChangeLog, License files - +- Handle encoding in setup file +- Update ReadME, ChangeLog, License files ## 0.0.6 Enhancement: - - Update for smart_truncate - +- Update for smart_truncate ## 0.0.5 Features: - - Added Python 3.2 and 3.3 support (work by: arthurdarcet@github) - +- Added Python 3.2 and 3.3 support (work by: arthurdarcet@github) ## 0.0.4 Features: - - Added option to choose non-dash separators (request by: danilodimoia@github) - +- Added option to choose non-dash separators (request by: danilodimoia@github) ## 0.0.3 Features: - - Added the ability to truncate slugs (request by: juanriaza@github) - +- Added the ability to truncate slugs (request by: juanriaza@github) ## 0.0.2 Enhancement: - - Incremental update - +- Incremental update ## 0.0.1 - - Initial version +- Initial version diff --git a/README.md b/README.md index 0e0195f..2305794 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -Python Slugify -==================== +# Python Slugify **A Python slugify application that handles unicode**. @@ -7,26 +6,29 @@ Python Slugify [![version-image]][version-link] [![coverage-image]][coverage-link] -Overview -==================== +# Overview **Best attempt** to create slugs from unicode strings while keeping it **DRY**. -Notice -==================== +# Notice -This module, by default installs and uses [text-unidecode](https://github.com/kmike/text-unidecode) *(GPL & Perl Artistic)* for its decoding needs. +This module, by default installs and uses [text-unidecode](https://github.com/kmike/text-unidecode) _(GPL & Perl Artistic)_ for its decoding needs. -However, there is an alternative decoding package called [Unidecode](https://github.com/avian2/unidecode) *(GPL)*. It can be installed as `python-slugify[unidecode]` for those who prefer it. +However, there is an alternative decoding package called [Unidecode](https://github.com/avian2/unidecode) _(GPL)_. It can be installed as `python-slugify[unidecode]` for those who prefer it. + +### Python Versions & `Official` Support + +- Python `2.7` <-> python-slugify `< 5.0.0` +- Python `3.6+` <-> python-slugify `>= 5.0.0` + +# How to install -How to install -==================== easy_install python-slugify |OR| easy_install python-slugify[unidecode] -- OR -- pip install python-slugify |OR| pip install python-slugify[unidecode] -Options -=================== +# Options + ```python def slugify( text, @@ -60,8 +62,7 @@ def slugify( """ ``` -How to use -==================== +# How to use ```python from slugify import slugify @@ -133,12 +134,10 @@ r = slugify(txt, replacements=[['Ü', 'UE'], ['ü', 'ue']]) self.assertEqual(r, "ueber-ueber-german-umlaut") ``` - -For more examples, have a look at the [test.py](test.py) file. +For more examples, have a look at the [test.py](test.py) file. -Command Line Options -==================== +# Command Line Options With the package, a command line tool called `slugify` is also installed. @@ -150,6 +149,7 @@ The command can take its input directly on the command line or from STDIN (when $ echo "Taking input from STDIN" | slugify --stdin taking-input-from-stdin ``` + ``` $ slugify taking input from the command line taking-input-from-the-command-line @@ -162,9 +162,7 @@ $ slugify --stopwords the in a hurry -- the quick brown fox jumps over the lazy quick-brown-fox-jumps-over-lazy-dog ``` - -Running the tests -==================== +# Running the tests To run the tests against all environments: @@ -174,21 +172,16 @@ To run the tests against the current environment: python test.py +# Contribution -Contribution -==================== - -Please read the ([wiki](https://github.com/un33k/python-slugify/wiki/Python-Slugify-Wiki)) page prior to raising any PRs. +Please read the ([wiki](https://github.com/un33k/python-slugify/wiki/Python-Slugify-Wiki)) page prior to raising any PRs. - -License -==================== +# License Released under a ([MIT](LICENSE)) license. +# Version -Version -==================== X.Y.Z Version `MAJOR` version -- when you make incompatible API changes, @@ -197,18 +190,13 @@ X.Y.Z Version [status-image]: https://travis-ci.org/un33k/python-slugify.svg?branch=master [status-link]: https://travis-ci.org/un33k/python-slugify - [version-image]: https://img.shields.io/pypi/v/python-slugify.svg [version-link]: https://pypi.python.org/pypi/python-slugify - [coverage-image]: https://coveralls.io/repos/un33k/python-slugify/badge.svg [coverage-link]: https://coveralls.io/r/un33k/python-slugify - [download-image]: https://img.shields.io/pypi/dm/python-slugify.svg [download-link]: https://pypi.python.org/pypi/python-slugify - -Sponsors -==================== +# Sponsors [Neekware Inc.](http://neekware.com) diff --git a/dev.requirements.txt b/dev.requirements.txt index f87605e..cbc5494 100644 --- a/dev.requirements.txt +++ b/dev.requirements.txt @@ -1 +1 @@ -pycodestyle==2.5.0 \ No newline at end of file +pycodestyle==2.7.0 \ No newline at end of file diff --git a/setup.py b/setup.py index 038a85f..07a660c 100755 --- a/setup.py +++ b/setup.py @@ -24,10 +24,7 @@ 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', @@ -67,7 +64,7 @@ def get_version(package): packages=find_packages(exclude=EXCLUDE_FROM_PACKAGES), install_requires=install_requires, extras_require=extras_require, - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', + python_requires='!=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*', classifiers=classifiers, entry_points={'console_scripts': ['slugify=slugify.__main__:main']}, ) diff --git a/slugify/__init__.py b/slugify/__init__.py index d69e2ed..3a11fd2 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -4,4 +4,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '4.0.1' +__version__ = '5.0.0' diff --git a/slugify/__main__.py b/slugify/__main__.py index a11989b..f815206 100644 --- a/slugify/__main__.py +++ b/slugify/__main__.py @@ -6,7 +6,7 @@ def parse_args(argv): - parser = argparse.ArgumentParser(description="Sluggify string") + parser = argparse.ArgumentParser(description="Slug string") input_group = parser.add_argument_group(description="Input") input_group.add_argument("input_string", nargs='*', diff --git a/tox.ini b/tox.ini index d189cbd..c200c2a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{39,38,37,36,35,27},pypy,pypy3 +envlist = py{39,38,37,36,35},pypy3 [testenv] deps= From f849a685cc7dacd13fb7b9d935fe5ca83b7130e9 Mon Sep 17 00:00:00 2001 From: Dmytro Litvinov Date: Sat, 1 May 2021 19:45:55 +0300 Subject: [PATCH 050/119] Update tox.ini Drop completly from tox.ini file Python3.5 for testing according to [CHANGELOG](https://github.com/un33k/python-slugify/blob/master/CHANGELOG.md#500). More information about it at [commit which should drop Python 3.5](https://github.com/un33k/python-slugify/commit/319559cb43f239ac0d3bc7b975a78059bf4a1086) --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index c200c2a..a4bee82 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{39,38,37,36,35},pypy3 +envlist = py{39,38,37,36},pypy3 [testenv] deps= @@ -15,4 +15,4 @@ commands = sh format.sh [testenv:coverage] deps = coverage commands = - coverage run --source=slugify test.py \ No newline at end of file + coverage run --source=slugify test.py From 663668942eed647957b0ddb8914ca152128418b2 Mon Sep 17 00:00:00 2001 From: Dmytro Litvinov Date: Sat, 1 May 2021 19:50:18 +0300 Subject: [PATCH 051/119] Simplify python_requires --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 07a660c..6745006 100755 --- a/setup.py +++ b/setup.py @@ -64,7 +64,7 @@ def get_version(package): packages=find_packages(exclude=EXCLUDE_FROM_PACKAGES), install_requires=install_requires, extras_require=extras_require, - python_requires='!=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*', + python_requires='>=3.6', classifiers=classifiers, entry_points={'console_scripts': ['slugify=slugify.__main__:main']}, ) From 377c6797a150151d07fabea77146be5bc9a732a5 Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Wed, 5 May 2021 19:22:50 -0400 Subject: [PATCH 052/119] clean up, post 2.7,3.5 drop --- .vscode/settings.json | 3 +-- CHANGELOG.md | 4 ++++ slugify/__init__.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 32531ea..2ab09c1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,4 @@ { "python.linting.pylintEnabled": false, - "restructuredtext.confPath": "", - "python.pythonPath": "/usr/local/opt/python/bin/python3.6" + "python.pythonPath": "/usr/bin/python3", } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 82ae711..d7d425b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.0.1 + +- Drop support for python 2.7, 3.5 & tox, clean up + ## 5.0.0 - Add support for Py 3.9 - added tox (@jon-betts - Thx) diff --git a/slugify/__init__.py b/slugify/__init__.py index 3a11fd2..a1193b8 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -4,4 +4,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '5.0.0' +__version__ = '5.0.1' From 744c7cd98948915158ea91d2ab13af038116043f Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Wed, 5 May 2021 19:30:56 -0400 Subject: [PATCH 053/119] add twine to dev.req.txt --- dev.requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev.requirements.txt b/dev.requirements.txt index cbc5494..337aa36 100644 --- a/dev.requirements.txt +++ b/dev.requirements.txt @@ -1 +1,2 @@ -pycodestyle==2.7.0 \ No newline at end of file +pycodestyle==2.7.0 +twine==3.4.1 \ No newline at end of file From 937779c77420f4acb8acd775bc2c35ed94f1393d Mon Sep 17 00:00:00 2001 From: Val Neekman Date: Wed, 5 May 2021 19:38:59 -0400 Subject: [PATCH 054/119] switch to twine for pushing to pypi --- CHANGELOG.md | 4 ++++ setup.py | 2 +- slugify/__init__.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7d425b..777f6dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.0.2 + +- Enable twine publish + ## 5.0.1 - Drop support for python 2.7, 3.5 & tox, clean up diff --git a/setup.py b/setup.py index 6745006..51b267f 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ def get_version(package): os.system("python setup.py sdist bdist_wheel") if sys.argv[-1] == 'publish': - os.system("python setup.py sdist upload") + os.system("python setup.py build && twine upload dist/*") args = {'version': get_version(package)} print("You probably want to also tag the version now:") print(" git tag -a %(version)s -m 'version %(version)s' && git push --tags" % args) diff --git a/slugify/__init__.py b/slugify/__init__.py index a1193b8..6c59f4e 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -4,4 +4,4 @@ __author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' __description__ = 'A Python slugify application that also handles Unicode' -__version__ = '5.0.1' +__version__ = '5.0.2' From 1097c2366ffcb4356ba84f74c7daf42bcfe720bf Mon Sep 17 00:00:00 2001 From: Fahrzin Hemmati Date: Mon, 10 May 2021 17:44:12 -0700 Subject: [PATCH 055/119] Add better typing for slugify.slugify Currently, mypy understands the type as `Iterable[str]`, which doesn't match what should actually be passed in, which is `Iterable[Iterable[str]]` or, ideally, `Iterable[Tuple[str, str]]` --- slugify/slugify.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/slugify/slugify.py b/slugify/slugify.py index bb3aa95..2097c41 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -1,5 +1,6 @@ import re import unicodedata +import typing import types import sys @@ -76,7 +77,7 @@ def smart_truncate(string, max_length=0, word_boundary=False, separator=' ', sav def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, word_boundary=False, separator=DEFAULT_SEPARATOR, save_order=False, stopwords=(), regex_pattern=None, lowercase=True, - replacements=()): + replacements: typing.Iterable[typing.Iterable[str]]=()): """ Make a slug from the given text. :param text (str): initial text From a90d96778a004a9f2fdc232debcd72fdb5bbf085 Mon Sep 17 00:00:00 2001 From: Fahrzin Hemmati Date: Mon, 10 May 2021 17:52:09 -0700 Subject: [PATCH 056/119] whitespace around = --- slugify/slugify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slugify/slugify.py b/slugify/slugify.py index 2097c41..6d4a67e 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -77,7 +77,7 @@ def smart_truncate(string, max_length=0, word_boundary=False, separator=' ', sav def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, word_boundary=False, separator=DEFAULT_SEPARATOR, save_order=False, stopwords=(), regex_pattern=None, lowercase=True, - replacements: typing.Iterable[typing.Iterable[str]]=()): + replacements: typing.Iterable[typing.Iterable[str]] = ()): """ Make a slug from the given text. :param text (str): initial text From 45e0ae3d0db0fce0c7e875491848a646f3011386 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 15:30:11 -0500 Subject: [PATCH 057/119] github actions --- .github/workflows/ci.yml | 42 +++++++++++++++++++++++++++++++++++++ .github/workflows/dev.yml | 43 ++++++++++++++++++++++++++++++++++++++ .github/workflows/main.yml | 42 +++++++++++++++++++++++++++++++++++++ setup.py | 1 + 4 files changed, 128 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/dev.yml create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..34ea50c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: (CI) + +on: + push: + branches: + - ci + +jobs: + build: + name: Setup ${{ matrix.python }} ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest, ubuntu-latest] + python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + steps: + - name: setup-python ${{ matrix.python }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install coveralls --upgrade + - name: Run flake8 + run: | + pip install flake8 --upgrade + flake8 --exclude=migrations,tests --ignore=E501,E241,E225,E128 . + - name: Run pycodestyle + run: | + pip install pycodestyle --upgrade + pycodestyle --exclude=migrations,tests --ignore=E501,E241,E225,E128 . + - name: Run test + run: | + coverage run --source=python test.py + - name: Coveralls + run: coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml new file mode 100644 index 0000000..ebfbe0a --- /dev/null +++ b/.github/workflows/dev.yml @@ -0,0 +1,43 @@ +name: (CI) + +on: + push: + branches: + - sandbox + - dev + +jobs: + build: + name: Setup ${{ matrix.python }} ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest, ubuntu-latest] + python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + steps: + - name: setup-python ${{ matrix.python }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install coveralls --upgrade + - name: Run flake8 + run: | + pip install flake8 --upgrade + flake8 --exclude=migrations,tests --ignore=E501,E241,E225,E128 . + - name: Run pycodestyle + run: | + pip install pycodestyle --upgrade + pycodestyle --exclude=migrations,tests --ignore=E501,E241,E225,E128 . + - name: Run test + run: | + coverage run --source=python test.py + - name: Coveralls + run: coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..7b7146b --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,42 @@ +name: (CI) + +on: + push: + branches: + - master + +jobs: + build: + name: Setup ${{ matrix.python }} ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest, ubuntu-latest] + python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + steps: + - name: setup-python ${{ matrix.python }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install coveralls --upgrade + - name: Run flake8 + run: | + pip install flake8 --upgrade + flake8 --exclude=migrations,tests --ignore=E501,E241,E225,E128 . + - name: Run pycodestyle + run: | + pip install pycodestyle --upgrade + pycodestyle --exclude=migrations,tests --ignore=E501,E241,E225,E128 . + - name: Run test + run: | + coverage run --source=python test.py + - name: Coveralls + run: coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/setup.py b/setup.py index 51b267f..769f5a1 100755 --- a/setup.py +++ b/setup.py @@ -29,6 +29,7 @@ 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', ] From 6404c1d5c66ac436ccc613eec338d4f548c00457 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 15:34:54 -0500 Subject: [PATCH 058/119] simplify action --- .github/workflows/dev.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index ebfbe0a..175facb 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -15,12 +15,13 @@ jobs: matrix: os: [macos-latest, windows-latest, ubuntu-latest] python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + steps: - - name: setup-python ${{ matrix.python }} + - uses: actions/checkout@v2 + - name: setup python uses: actions/setup-python@v2 with: python-version: ${{ matrix.python }} - - name: Install dependencies run: | python -m pip install --upgrade pip From 9b9b68f512edecf209d9af977d2080194ff55d88 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 15:38:40 -0500 Subject: [PATCH 059/119] git actions --- .github/workflows/dev.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 175facb..91929f7 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -1,5 +1,7 @@ -name: (CI) +name: DEV +# Run on push only for dev/sandbox +# Otherwise it may trigger concurrently `push & pull_request` on PRs. on: push: branches: @@ -8,12 +10,10 @@ on: jobs: build: - name: Setup ${{ matrix.python }} ${{ matrix.os }} - runs-on: ${{ matrix.os }} + name: Python ${{ matrix.python-version }} + runs-on: ubuntu-latest strategy: - fail-fast: false matrix: - os: [macos-latest, windows-latest, ubuntu-latest] python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] steps: From ad33581c491fd16cd64930fe02c862c04595550b Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 16:11:08 -0500 Subject: [PATCH 060/119] version file, github action --- .github/workflows/dev.yml | 4 ++-- .vscode/settings.json | 3 ++- dev.requirements.txt | 3 ++- setup.py | 2 +- slugify/__init__.py | 5 ----- slugify/__main__.py | 4 ++-- slugify/__version__.py | 3 +++ slugify/slugify.py | 28 +++++++++------------------- slugify/special.py | 2 +- test.py | 1 - 10 files changed, 22 insertions(+), 33 deletions(-) create mode 100644 slugify/__version__.py diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 91929f7..fc6d0a5 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -30,11 +30,11 @@ jobs: - name: Run flake8 run: | pip install flake8 --upgrade - flake8 --exclude=migrations,tests --ignore=E501,E241,E225,E128 . + flake8 --exclude=build --ignore=E501,F403,F401,E241,E225,E128 . - name: Run pycodestyle run: | pip install pycodestyle --upgrade - pycodestyle --exclude=migrations,tests --ignore=E501,E241,E225,E128 . + pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py - name: Run test run: | coverage run --source=python test.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 2ab09c1..ecfbb80 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "python.linting.pylintEnabled": false, "python.pythonPath": "/usr/bin/python3", -} \ No newline at end of file + "cSpell.words": ["Neekman", "shch", "xlate"] +} diff --git a/dev.requirements.txt b/dev.requirements.txt index 337aa36..2b4e781 100644 --- a/dev.requirements.txt +++ b/dev.requirements.txt @@ -1,2 +1,3 @@ pycodestyle==2.7.0 -twine==3.4.1 \ No newline at end of file +twine==3.4.1 +flake8==4.0.1 \ No newline at end of file diff --git a/setup.py b/setup.py index 769f5a1..8adc159 100755 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ def get_version(package): """ Return package version as listed in `__version__` in `init.py`. """ - init_py = codecs.open(os.path.join(package, '__init__.py'), encoding='utf-8').read() + init_py = codecs.open(os.path.join(package, '__version__.py'), encoding='utf-8').read() return re.search("^__version__ = ['\"]([^'\"]+)['\"]", init_py, re.MULTILINE).group(1) diff --git a/slugify/__init__.py b/slugify/__init__.py index 6c59f4e..ac21492 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -1,7 +1,2 @@ from .special import * from .slugify import * - - -__author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' -__description__ = 'A Python slugify application that also handles Unicode' -__version__ = '5.0.2' diff --git a/slugify/__main__.py b/slugify/__main__.py index f815206..ffdfd5f 100644 --- a/slugify/__main__.py +++ b/slugify/__main__.py @@ -77,7 +77,7 @@ def slugify_params(args): ) -def main(argv=None): # pragma: no cover +def main(argv=None): # pragma: no cover """ Run this program """ if argv is None: argv = sys.argv @@ -89,5 +89,5 @@ def main(argv=None): # pragma: no cover sys.exit(-1) -if __name__ == '__main__': # pragma: no cover +if __name__ == '__main__': # pragma: no cover main() diff --git a/slugify/__version__.py b/slugify/__version__.py new file mode 100644 index 0000000..72729f9 --- /dev/null +++ b/slugify/__version__.py @@ -0,0 +1,3 @@ +__author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' +__description__ = 'A Python slugify application that also handles Unicode' +__version__ = '6.0.0' diff --git a/slugify/slugify.py b/slugify/slugify.py index bb3aa95..f38df10 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -1,17 +1,7 @@ import re import unicodedata -import types import sys - -try: - from htmlentitydefs import name2codepoint - _unicode = unicode - _unicode_type = types.UnicodeType -except ImportError: - from html.entities import name2codepoint - _unicode = str - _unicode_type = str - unichr = chr +from html.entities import name2codepoint try: import text_unidecode as unidecode @@ -69,7 +59,7 @@ def smart_truncate(string, max_length=0, word_boundary=False, separator=' ', sav else: if save_order: break - if not truncated: # pragma: no cover + if not truncated: # pragma: no cover truncated = string[:max_length] return truncated.strip(separator) @@ -100,8 +90,8 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w text = text.replace(old, new) # ensure text is unicode - if not isinstance(text, _unicode_type): - text = _unicode(text, 'utf-8', 'ignore') + if not isinstance(text, str): + text = str(text, 'utf-8', 'ignore') # replace quotes with dashes - pre-process text = QUOTE_PATTERN.sub(DEFAULT_SEPARATOR, text) @@ -110,24 +100,24 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w text = unidecode.unidecode(text) # ensure text is still in unicode - if not isinstance(text, _unicode_type): - text = _unicode(text, 'utf-8', 'ignore') + if not isinstance(text, str): + text = str(text, 'utf-8', 'ignore') # character entity reference if entities: - text = CHAR_ENTITY_PATTERN.sub(lambda m: unichr(name2codepoint[m.group(1)]), text) + text = CHAR_ENTITY_PATTERN.sub(lambda m: chr(name2codepoint[m.group(1)]), text) # decimal character reference if decimal: try: - text = DECIMAL_PATTERN.sub(lambda m: unichr(int(m.group(1))), text) + text = DECIMAL_PATTERN.sub(lambda m: chr(int(m.group(1))), text) except Exception: pass # hexadecimal character reference if hexadecimal: try: - text = HEX_PATTERN.sub(lambda m: unichr(int(m.group(1), 16)), text) + text = HEX_PATTERN.sub(lambda m: chr(int(m.group(1), 16)), text) except Exception: pass diff --git a/slugify/special.py b/slugify/special.py index d3478d5..54eb85c 100644 --- a/slugify/special.py +++ b/slugify/special.py @@ -20,7 +20,7 @@ def add_uppercase_char(char_list): (u'я', u'ya'), # ia (u'х', u'h'), # kh (u'у', u'y'), # u - (u'щ', u'sch'), # shch + (u'щ', u'sch'), # sch (u'ю', u'u'), # iu / yu ] CYRILLIC = add_uppercase_char(_CYRILLIC) diff --git a/test.py b/test.py index ddf1bf4..1cd56dc 100644 --- a/test.py +++ b/test.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- import io -import os import sys import unittest from contextlib import contextmanager From 614f1830dece4140f51e7797670306f718ee7050 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 16:13:33 -0500 Subject: [PATCH 061/119] actions --- .github/workflows/dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index fc6d0a5..56d7302 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -10,7 +10,7 @@ on: jobs: build: - name: Python ${{ matrix.python-version }} + name: Python ${{ matrix.python }} runs-on: ubuntu-latest strategy: matrix: From d5e3586ca156d9f92a5d8b8195dfc132218a70e1 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 16:17:39 -0500 Subject: [PATCH 062/119] fix coverage --- .github/workflows/dev.yml | 2 +- .travis.yml | 22 ---------------------- 2 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 .travis.yml diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 56d7302..d0cb401 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -37,7 +37,7 @@ jobs: pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py - name: Run test run: | - coverage run --source=python test.py + coverage run --source=slugify test.py - name: Coveralls run: coveralls --service=github env: diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2f8b3ce..0000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: python -dist: xenial - -python: - - "3.6" - - "3.7" - - "3.8" - - "3.9" - - "pypy3" - -install: - - pip install pip -U - - pip install -e . - - pip install pycodestyle - - pip install coveralls - -before_script: - - "bash format.sh" - -script: coverage run --source=slugify test.py - -after_success: coveralls From 9ada774c34b8b9939d5a83d42a798d0e4f82c843 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 16:23:46 -0500 Subject: [PATCH 063/119] actions --- CHANGELOG.md | 4 ++++ README.md | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 777f6dc..ed1f80f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.0.0 + +- Enable github action + ## 5.0.2 - Enable twine publish diff --git a/README.md b/README.md index 2305794..5476fbe 100644 --- a/README.md +++ b/README.md @@ -188,8 +188,8 @@ X.Y.Z Version `MINOR` version -- when you add functionality in a backwards-compatible manner, and `PATCH` version -- when you make backwards-compatible bug fixes. -[status-image]: https://travis-ci.org/un33k/python-slugify.svg?branch=master -[status-link]: https://travis-ci.org/un33k/python-slugify +[status-image]: https://github.com/un33k/python-slugify/actions/workflows/ci.yml/badge.svg +[status-link]: https://github.com/un33k/python-slugify/actions/workflows/ci.yml [version-image]: https://img.shields.io/pypi/v/python-slugify.svg [version-link]: https://pypi.python.org/pypi/python-slugify [coverage-image]: https://coveralls.io/repos/un33k/python-slugify/badge.svg From 7534199cf4b214309619e7486e1de1057bff74a3 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 16:30:41 -0500 Subject: [PATCH 064/119] version --- setup.py | 121 +++++++++++++++++++++++------------------ slugify/__version__.py | 7 ++- 2 files changed, 73 insertions(+), 55 deletions(-) diff --git a/setup.py b/setup.py index 8adc159..d9bd6fe 100755 --- a/setup.py +++ b/setup.py @@ -1,71 +1,84 @@ #!/usr/bin/env python - -# -*- coding: utf-8 -*- -from setuptools import setup, find_packages -import re +# Learn more: https://github.com/un33k/setup.py import os import sys -import codecs -name = 'python-slugify' +from codecs import open +from shutil import rmtree +from setuptools import setup + + package = 'slugify' -description = 'A Python Slugify application that handles Unicode' -url = 'https://github.com/un33k/python-slugify' -author = 'Val Neekman' -author_email = 'info@neekware.com' -license = 'MIT' +python_requires = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +here = os.path.abspath(os.path.dirname(__file__)) + install_requires = ['text-unidecode>=1.3'] -extras_require = {'unidecode': ['Unidecode>=1.1.1']} +extras_requires = {'unidecode': ['Unidecode>=1.1.1']} +test_requires = [] -classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Build Tools', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', -] +about = {} +with open(os.path.join(here, package, '__version__.py'), 'r', 'utf-8') as f: + exec(f.read(), about) +with open('README.md', 'r', 'utf-8') as f: + readme = f.read() -def get_version(package): - """ - Return package version as listed in `__version__` in `init.py`. - """ - init_py = codecs.open(os.path.join(package, '__version__.py'), encoding='utf-8').read() - return re.search("^__version__ = ['\"]([^'\"]+)['\"]", init_py, re.MULTILINE).group(1) +def status(s): + print('\033[1m{0}\033[0m'.format(s)) -if sys.argv[-1] == 'build': - os.system("python setup.py sdist bdist_wheel") +# 'setup.py publish' shortcut. if sys.argv[-1] == 'publish': - os.system("python setup.py build && twine upload dist/*") - args = {'version': get_version(package)} - print("You probably want to also tag the version now:") - print(" git tag -a %(version)s -m 'version %(version)s' && git push --tags" % args) - sys.exit() + try: + status('Removing previous builds…') + rmtree(os.path.join(here, 'dist')) + except OSError: + pass + + status('Building Source and Wheel (universal) distribution…') + os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable)) -EXCLUDE_FROM_PACKAGES = [] + status('Uploading the package to PyPI via Twine…') + os.system('twine upload dist/*') + + status('Pushing git tags…') + os.system('git tag v{0}'.format(about['__version__'])) + os.system('git push --tags') + sys.exit() setup( - name=name, - version=get_version(package), - url=url, - license=license, - description=description, - long_description=description, - author=author, - author_email=author_email, - packages=find_packages(exclude=EXCLUDE_FROM_PACKAGES), + name=about['__title__'], + version=about['__version__'], + description=about['__description__'], + long_description=readme, + long_description_content_type='text/markdown', + author=about['__author__'], + author_email=about['__author_email__'], + url=about['__url__'], + license=about['__license__'], + packages=[package], + package_data={'': ['LICENSE']}, + package_dir={'slugify': 'slugify'}, + include_package_data=True, + python_requires=python_requires, install_requires=install_requires, - extras_require=extras_require, - python_requires='>=3.6', - classifiers=classifiers, - entry_points={'console_scripts': ['slugify=slugify.__main__:main']}, -) + tests_require=test_requires, + extras_require=extras_requires, + zip_safe=False, + cmdclass={}, + project_urls={}, + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Natural Language :: English', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + ] +) \ No newline at end of file diff --git a/slugify/__version__.py b/slugify/__version__.py index 72729f9..4e5471b 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -1,3 +1,8 @@ -__author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' +__title__ = 'python-slugify' +__author__ = 'Val Neekman' +__author_email__ = 'info@neekware.com' __description__ = 'A Python slugify application that also handles Unicode' +__url__ = 'https://github.com/un33k/python-slugify' +__license__ = 'MIT' +__copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' __version__ = '6.0.0' From 97915d932c40b3783dc0064c07f247bba1fb6929 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 16:32:21 -0500 Subject: [PATCH 065/119] manifest --- MANIFEST.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 0c78f18..373701c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,3 @@ -include CHANGELOG.md include LICENSE include README.md -include test.py +include CHANGELOG.md From 86161b412e4674fcd5bee4c2120d6fe2cd7fa18d Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 16:33:14 -0500 Subject: [PATCH 066/119] github action --- .github/workflows/ci.yml | 21 +++++++++++---------- .github/workflows/main.yml | 21 +++++++++++---------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34ea50c..d4998a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,7 @@ -name: (CI) +name: CI +# Run on push only for dev/sandbox +# Otherwise it may trigger concurrently `push & pull_request` on PRs. on: push: branches: @@ -7,19 +9,18 @@ on: jobs: build: - name: Setup ${{ matrix.python }} ${{ matrix.os }} - runs-on: ${{ matrix.os }} + name: Python ${{ matrix.python }} + runs-on: ubuntu-latest strategy: - fail-fast: false matrix: - os: [macos-latest, windows-latest, ubuntu-latest] python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + steps: - - name: setup-python ${{ matrix.python }} + - uses: actions/checkout@v2 + - name: setup python uses: actions/setup-python@v2 with: python-version: ${{ matrix.python }} - - name: Install dependencies run: | python -m pip install --upgrade pip @@ -28,14 +29,14 @@ jobs: - name: Run flake8 run: | pip install flake8 --upgrade - flake8 --exclude=migrations,tests --ignore=E501,E241,E225,E128 . + flake8 --exclude=build --ignore=E501,F403,F401,E241,E225,E128 . - name: Run pycodestyle run: | pip install pycodestyle --upgrade - pycodestyle --exclude=migrations,tests --ignore=E501,E241,E225,E128 . + pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py - name: Run test run: | - coverage run --source=python test.py + coverage run --source=slugify test.py - name: Coveralls run: coveralls --service=github env: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7b7146b..f1e75b7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,5 +1,7 @@ -name: (CI) +name: Main +# Run on push only for dev/sandbox +# Otherwise it may trigger concurrently `push & pull_request` on PRs. on: push: branches: @@ -7,19 +9,18 @@ on: jobs: build: - name: Setup ${{ matrix.python }} ${{ matrix.os }} - runs-on: ${{ matrix.os }} + name: Python ${{ matrix.python }} + runs-on: ubuntu-latest strategy: - fail-fast: false matrix: - os: [macos-latest, windows-latest, ubuntu-latest] python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + steps: - - name: setup-python ${{ matrix.python }} + - uses: actions/checkout@v2 + - name: setup python uses: actions/setup-python@v2 with: python-version: ${{ matrix.python }} - - name: Install dependencies run: | python -m pip install --upgrade pip @@ -28,14 +29,14 @@ jobs: - name: Run flake8 run: | pip install flake8 --upgrade - flake8 --exclude=migrations,tests --ignore=E501,E241,E225,E128 . + flake8 --exclude=build --ignore=E501,F403,F401,E241,E225,E128 . - name: Run pycodestyle run: | pip install pycodestyle --upgrade - pycodestyle --exclude=migrations,tests --ignore=E501,E241,E225,E128 . + pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py - name: Run test run: | - coverage run --source=python test.py + coverage run --source=slugify test.py - name: Coveralls run: coveralls --service=github env: From 8ea91da9f4c5e4ba12912ad2dff804abc4045e10 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 16:34:57 -0500 Subject: [PATCH 067/119] setup --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d9bd6fe..8fdb214 100755 --- a/setup.py +++ b/setup.py @@ -81,4 +81,4 @@ def status(s): 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', ] -) \ No newline at end of file +) From 7c4802eef3fdd2762d24b7f031f0d0e9305ad15d Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 16:47:31 -0500 Subject: [PATCH 068/119] coverall coverage --- setup.cfg | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.cfg b/setup.cfg index 3c6e79c..26da821 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,7 @@ [bdist_wheel] universal=1 + +[coverage:run] +include = + slugify/slugify.py + slugify/special.py From c23bbc1dfbd50c64c342f9e6daf488b94639e4ba Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 16:51:48 -0500 Subject: [PATCH 069/119] coverall omit --- .github/workflows/dev.yml | 2 +- setup.cfg | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index d0cb401..729c26c 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -37,7 +37,7 @@ jobs: pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py - name: Run test run: | - coverage run --source=slugify test.py + coverage run --include=slugify --omit=__version__.py --source=slugify test.py - name: Coveralls run: coveralls --service=github env: diff --git a/setup.cfg b/setup.cfg index 26da821..3c6e79c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,2 @@ [bdist_wheel] universal=1 - -[coverage:run] -include = - slugify/slugify.py - slugify/special.py From 57ee951f7c1c0fe48a75b198031a98046ab458fc Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 16:57:29 -0500 Subject: [PATCH 070/119] coverage run --- .github/workflows/dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 729c26c..d0cb401 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -37,7 +37,7 @@ jobs: pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py - name: Run test run: | - coverage run --include=slugify --omit=__version__.py --source=slugify test.py + coverage run --source=slugify test.py - name: Coveralls run: coveralls --service=github env: From 28ac37cd980e80c12bfa950d8680ff9d4826678f Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 17:05:29 -0500 Subject: [PATCH 071/119] remove tox --- .python-version | 5 ----- test.py | 2 +- tox.ini | 18 ------------------ 3 files changed, 1 insertion(+), 24 deletions(-) delete mode 100644 .python-version delete mode 100644 tox.ini diff --git a/.python-version b/.python-version deleted file mode 100644 index a8733ab..0000000 --- a/.python-version +++ /dev/null @@ -1,5 +0,0 @@ -3.9.2 -3.8.8 -3.7.10 -3.6.13 -pypy3.7-7.3.3 diff --git a/test.py b/test.py index 1cd56dc..752c499 100644 --- a/test.py +++ b/test.py @@ -9,7 +9,7 @@ from slugify.__main__ import slugify_params, parse_args -class TestSlugification(unittest.TestCase): +class TestSlugify(unittest.TestCase): def test_extraneous_seperators(self): diff --git a/tox.ini b/tox.ini deleted file mode 100644 index a4bee82..0000000 --- a/tox.ini +++ /dev/null @@ -1,18 +0,0 @@ -[tox] -envlist = py{39,38,37,36},pypy3 - -[testenv] -deps= - -e . -commands = - python -m unittest test - -[testenv:format] -deps = pycodestyle -allowlist_externals = sh -commands = sh format.sh - -[testenv:coverage] -deps = coverage -commands = - coverage run --source=slugify test.py From 341aa39e801319c5c9de03a06c519466c4b9867e Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 17:09:20 -0500 Subject: [PATCH 072/119] clean readme --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 5476fbe..af10080 100644 --- a/README.md +++ b/README.md @@ -164,10 +164,6 @@ quick-brown-fox-jumps-over-lazy-dog # Running the tests -To run the tests against all environments: - - tox - To run the tests against the current environment: python test.py From bfb5170094e8065fcb113e32936ea356f7f420a2 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 17:11:23 -0500 Subject: [PATCH 073/119] clean up --- .python-version | 5 ----- README.md | 4 ---- test.py | 2 +- tox.ini | 18 ------------------ 4 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 .python-version delete mode 100644 tox.ini diff --git a/.python-version b/.python-version deleted file mode 100644 index a8733ab..0000000 --- a/.python-version +++ /dev/null @@ -1,5 +0,0 @@ -3.9.2 -3.8.8 -3.7.10 -3.6.13 -pypy3.7-7.3.3 diff --git a/README.md b/README.md index 5476fbe..af10080 100644 --- a/README.md +++ b/README.md @@ -164,10 +164,6 @@ quick-brown-fox-jumps-over-lazy-dog # Running the tests -To run the tests against all environments: - - tox - To run the tests against the current environment: python test.py diff --git a/test.py b/test.py index 1cd56dc..752c499 100644 --- a/test.py +++ b/test.py @@ -9,7 +9,7 @@ from slugify.__main__ import slugify_params, parse_args -class TestSlugification(unittest.TestCase): +class TestSlugify(unittest.TestCase): def test_extraneous_seperators(self): diff --git a/tox.ini b/tox.ini deleted file mode 100644 index a4bee82..0000000 --- a/tox.ini +++ /dev/null @@ -1,18 +0,0 @@ -[tox] -envlist = py{39,38,37,36},pypy3 - -[testenv] -deps= - -e . -commands = - python -m unittest test - -[testenv:format] -deps = pycodestyle -allowlist_externals = sh -commands = sh format.sh - -[testenv:coverage] -deps = coverage -commands = - coverage run --source=slugify test.py From e6e488d573ebbb6b3388529066652ebf41056410 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 17:12:23 -0500 Subject: [PATCH 074/119] change log --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed1f80f..8053b87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 6.0.0 - Enable github action +- Remove tox, as we run the test on github action, the end users can refer to those test ## 5.0.2 From f4c176e043c8413db2914d0ff132a9f014a2f5ec Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 17:14:15 -0500 Subject: [PATCH 075/119] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed1f80f..8053b87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 6.0.0 - Enable github action +- Remove tox, as we run the test on github action, the end users can refer to those test ## 5.0.2 From 86d76eead5430ec9b36b178dc0f3cb9b8ec20127 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 17:20:50 -0500 Subject: [PATCH 076/119] add github action, cleanup --- .github/workflows/ci.yml | 43 ++++++++++++++ .github/workflows/dev.yml | 44 ++++++++++++++ .github/workflows/main.yml | 43 ++++++++++++++ .python-version | 5 -- .travis.yml | 22 ------- .vscode/settings.json | 3 +- CHANGELOG.md | 5 ++ MANIFEST.in | 3 +- README.md | 8 +-- dev.requirements.txt | 3 +- setup.py | 118 +++++++++++++++++++++---------------- slugify/__init__.py | 5 -- slugify/__main__.py | 4 +- slugify/__version__.py | 8 +++ slugify/slugify.py | 31 ++++------ slugify/special.py | 2 +- test.py | 3 +- tox.ini | 18 ------ 18 files changed, 230 insertions(+), 138 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/dev.yml create mode 100644 .github/workflows/main.yml delete mode 100644 .python-version delete mode 100644 .travis.yml create mode 100644 slugify/__version__.py delete mode 100644 tox.ini diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d4998a5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +name: CI + +# Run on push only for dev/sandbox +# Otherwise it may trigger concurrently `push & pull_request` on PRs. +on: + push: + branches: + - ci + +jobs: + build: + name: Python ${{ matrix.python }} + runs-on: ubuntu-latest + strategy: + matrix: + python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + + steps: + - uses: actions/checkout@v2 + - name: setup python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install coveralls --upgrade + - name: Run flake8 + run: | + pip install flake8 --upgrade + flake8 --exclude=build --ignore=E501,F403,F401,E241,E225,E128 . + - name: Run pycodestyle + run: | + pip install pycodestyle --upgrade + pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py + - name: Run test + run: | + coverage run --source=slugify test.py + - name: Coveralls + run: coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml new file mode 100644 index 0000000..d0cb401 --- /dev/null +++ b/.github/workflows/dev.yml @@ -0,0 +1,44 @@ +name: DEV + +# Run on push only for dev/sandbox +# Otherwise it may trigger concurrently `push & pull_request` on PRs. +on: + push: + branches: + - sandbox + - dev + +jobs: + build: + name: Python ${{ matrix.python }} + runs-on: ubuntu-latest + strategy: + matrix: + python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + + steps: + - uses: actions/checkout@v2 + - name: setup python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install coveralls --upgrade + - name: Run flake8 + run: | + pip install flake8 --upgrade + flake8 --exclude=build --ignore=E501,F403,F401,E241,E225,E128 . + - name: Run pycodestyle + run: | + pip install pycodestyle --upgrade + pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py + - name: Run test + run: | + coverage run --source=slugify test.py + - name: Coveralls + run: coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..f1e75b7 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,43 @@ +name: Main + +# Run on push only for dev/sandbox +# Otherwise it may trigger concurrently `push & pull_request` on PRs. +on: + push: + branches: + - master + +jobs: + build: + name: Python ${{ matrix.python }} + runs-on: ubuntu-latest + strategy: + matrix: + python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + + steps: + - uses: actions/checkout@v2 + - name: setup python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install coveralls --upgrade + - name: Run flake8 + run: | + pip install flake8 --upgrade + flake8 --exclude=build --ignore=E501,F403,F401,E241,E225,E128 . + - name: Run pycodestyle + run: | + pip install pycodestyle --upgrade + pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py + - name: Run test + run: | + coverage run --source=slugify test.py + - name: Coveralls + run: coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.python-version b/.python-version deleted file mode 100644 index a8733ab..0000000 --- a/.python-version +++ /dev/null @@ -1,5 +0,0 @@ -3.9.2 -3.8.8 -3.7.10 -3.6.13 -pypy3.7-7.3.3 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2f8b3ce..0000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: python -dist: xenial - -python: - - "3.6" - - "3.7" - - "3.8" - - "3.9" - - "pypy3" - -install: - - pip install pip -U - - pip install -e . - - pip install pycodestyle - - pip install coveralls - -before_script: - - "bash format.sh" - -script: coverage run --source=slugify test.py - -after_success: coveralls diff --git a/.vscode/settings.json b/.vscode/settings.json index 2ab09c1..ecfbb80 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "python.linting.pylintEnabled": false, "python.pythonPath": "/usr/bin/python3", -} \ No newline at end of file + "cSpell.words": ["Neekman", "shch", "xlate"] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 777f6dc..8053b87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 6.0.0 + +- Enable github action +- Remove tox, as we run the test on github action, the end users can refer to those test + ## 5.0.2 - Enable twine publish diff --git a/MANIFEST.in b/MANIFEST.in index 0c78f18..373701c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,3 @@ -include CHANGELOG.md include LICENSE include README.md -include test.py +include CHANGELOG.md diff --git a/README.md b/README.md index 2305794..af10080 100644 --- a/README.md +++ b/README.md @@ -164,10 +164,6 @@ quick-brown-fox-jumps-over-lazy-dog # Running the tests -To run the tests against all environments: - - tox - To run the tests against the current environment: python test.py @@ -188,8 +184,8 @@ X.Y.Z Version `MINOR` version -- when you add functionality in a backwards-compatible manner, and `PATCH` version -- when you make backwards-compatible bug fixes. -[status-image]: https://travis-ci.org/un33k/python-slugify.svg?branch=master -[status-link]: https://travis-ci.org/un33k/python-slugify +[status-image]: https://github.com/un33k/python-slugify/actions/workflows/ci.yml/badge.svg +[status-link]: https://github.com/un33k/python-slugify/actions/workflows/ci.yml [version-image]: https://img.shields.io/pypi/v/python-slugify.svg [version-link]: https://pypi.python.org/pypi/python-slugify [coverage-image]: https://coveralls.io/repos/un33k/python-slugify/badge.svg diff --git a/dev.requirements.txt b/dev.requirements.txt index 337aa36..2b4e781 100644 --- a/dev.requirements.txt +++ b/dev.requirements.txt @@ -1,2 +1,3 @@ pycodestyle==2.7.0 -twine==3.4.1 \ No newline at end of file +twine==3.4.1 +flake8==4.0.1 \ No newline at end of file diff --git a/setup.py b/setup.py index 51b267f..8fdb214 100755 --- a/setup.py +++ b/setup.py @@ -1,70 +1,84 @@ #!/usr/bin/env python - -# -*- coding: utf-8 -*- -from setuptools import setup, find_packages -import re +# Learn more: https://github.com/un33k/setup.py import os import sys -import codecs -name = 'python-slugify' +from codecs import open +from shutil import rmtree +from setuptools import setup + + package = 'slugify' -description = 'A Python Slugify application that handles Unicode' -url = 'https://github.com/un33k/python-slugify' -author = 'Val Neekman' -author_email = 'info@neekware.com' -license = 'MIT' +python_requires = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +here = os.path.abspath(os.path.dirname(__file__)) + install_requires = ['text-unidecode>=1.3'] -extras_require = {'unidecode': ['Unidecode>=1.1.1']} +extras_requires = {'unidecode': ['Unidecode>=1.1.1']} +test_requires = [] -classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Build Tools', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', -] +about = {} +with open(os.path.join(here, package, '__version__.py'), 'r', 'utf-8') as f: + exec(f.read(), about) +with open('README.md', 'r', 'utf-8') as f: + readme = f.read() -def get_version(package): - """ - Return package version as listed in `__version__` in `init.py`. - """ - init_py = codecs.open(os.path.join(package, '__init__.py'), encoding='utf-8').read() - return re.search("^__version__ = ['\"]([^'\"]+)['\"]", init_py, re.MULTILINE).group(1) +def status(s): + print('\033[1m{0}\033[0m'.format(s)) -if sys.argv[-1] == 'build': - os.system("python setup.py sdist bdist_wheel") +# 'setup.py publish' shortcut. if sys.argv[-1] == 'publish': - os.system("python setup.py build && twine upload dist/*") - args = {'version': get_version(package)} - print("You probably want to also tag the version now:") - print(" git tag -a %(version)s -m 'version %(version)s' && git push --tags" % args) - sys.exit() + try: + status('Removing previous builds…') + rmtree(os.path.join(here, 'dist')) + except OSError: + pass + + status('Building Source and Wheel (universal) distribution…') + os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable)) -EXCLUDE_FROM_PACKAGES = [] + status('Uploading the package to PyPI via Twine…') + os.system('twine upload dist/*') + + status('Pushing git tags…') + os.system('git tag v{0}'.format(about['__version__'])) + os.system('git push --tags') + sys.exit() setup( - name=name, - version=get_version(package), - url=url, - license=license, - description=description, - long_description=description, - author=author, - author_email=author_email, - packages=find_packages(exclude=EXCLUDE_FROM_PACKAGES), + name=about['__title__'], + version=about['__version__'], + description=about['__description__'], + long_description=readme, + long_description_content_type='text/markdown', + author=about['__author__'], + author_email=about['__author_email__'], + url=about['__url__'], + license=about['__license__'], + packages=[package], + package_data={'': ['LICENSE']}, + package_dir={'slugify': 'slugify'}, + include_package_data=True, + python_requires=python_requires, install_requires=install_requires, - extras_require=extras_require, - python_requires='>=3.6', - classifiers=classifiers, - entry_points={'console_scripts': ['slugify=slugify.__main__:main']}, + tests_require=test_requires, + extras_require=extras_requires, + zip_safe=False, + cmdclass={}, + project_urls={}, + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Natural Language :: English', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + ] ) diff --git a/slugify/__init__.py b/slugify/__init__.py index 6c59f4e..ac21492 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -1,7 +1,2 @@ from .special import * from .slugify import * - - -__author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' -__description__ = 'A Python slugify application that also handles Unicode' -__version__ = '5.0.2' diff --git a/slugify/__main__.py b/slugify/__main__.py index f815206..ffdfd5f 100644 --- a/slugify/__main__.py +++ b/slugify/__main__.py @@ -77,7 +77,7 @@ def slugify_params(args): ) -def main(argv=None): # pragma: no cover +def main(argv=None): # pragma: no cover """ Run this program """ if argv is None: argv = sys.argv @@ -89,5 +89,5 @@ def main(argv=None): # pragma: no cover sys.exit(-1) -if __name__ == '__main__': # pragma: no cover +if __name__ == '__main__': # pragma: no cover main() diff --git a/slugify/__version__.py b/slugify/__version__.py new file mode 100644 index 0000000..4e5471b --- /dev/null +++ b/slugify/__version__.py @@ -0,0 +1,8 @@ +__title__ = 'python-slugify' +__author__ = 'Val Neekman' +__author_email__ = 'info@neekware.com' +__description__ = 'A Python slugify application that also handles Unicode' +__url__ = 'https://github.com/un33k/python-slugify' +__license__ = 'MIT' +__copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' +__version__ = '6.0.0' diff --git a/slugify/slugify.py b/slugify/slugify.py index 6d4a67e..f38df10 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -1,18 +1,7 @@ import re import unicodedata -import typing -import types import sys - -try: - from htmlentitydefs import name2codepoint - _unicode = unicode - _unicode_type = types.UnicodeType -except ImportError: - from html.entities import name2codepoint - _unicode = str - _unicode_type = str - unichr = chr +from html.entities import name2codepoint try: import text_unidecode as unidecode @@ -70,14 +59,14 @@ def smart_truncate(string, max_length=0, word_boundary=False, separator=' ', sav else: if save_order: break - if not truncated: # pragma: no cover + if not truncated: # pragma: no cover truncated = string[:max_length] return truncated.strip(separator) def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, word_boundary=False, separator=DEFAULT_SEPARATOR, save_order=False, stopwords=(), regex_pattern=None, lowercase=True, - replacements: typing.Iterable[typing.Iterable[str]] = ()): + replacements=()): """ Make a slug from the given text. :param text (str): initial text @@ -101,8 +90,8 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w text = text.replace(old, new) # ensure text is unicode - if not isinstance(text, _unicode_type): - text = _unicode(text, 'utf-8', 'ignore') + if not isinstance(text, str): + text = str(text, 'utf-8', 'ignore') # replace quotes with dashes - pre-process text = QUOTE_PATTERN.sub(DEFAULT_SEPARATOR, text) @@ -111,24 +100,24 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w text = unidecode.unidecode(text) # ensure text is still in unicode - if not isinstance(text, _unicode_type): - text = _unicode(text, 'utf-8', 'ignore') + if not isinstance(text, str): + text = str(text, 'utf-8', 'ignore') # character entity reference if entities: - text = CHAR_ENTITY_PATTERN.sub(lambda m: unichr(name2codepoint[m.group(1)]), text) + text = CHAR_ENTITY_PATTERN.sub(lambda m: chr(name2codepoint[m.group(1)]), text) # decimal character reference if decimal: try: - text = DECIMAL_PATTERN.sub(lambda m: unichr(int(m.group(1))), text) + text = DECIMAL_PATTERN.sub(lambda m: chr(int(m.group(1))), text) except Exception: pass # hexadecimal character reference if hexadecimal: try: - text = HEX_PATTERN.sub(lambda m: unichr(int(m.group(1), 16)), text) + text = HEX_PATTERN.sub(lambda m: chr(int(m.group(1), 16)), text) except Exception: pass diff --git a/slugify/special.py b/slugify/special.py index d3478d5..54eb85c 100644 --- a/slugify/special.py +++ b/slugify/special.py @@ -20,7 +20,7 @@ def add_uppercase_char(char_list): (u'я', u'ya'), # ia (u'х', u'h'), # kh (u'у', u'y'), # u - (u'щ', u'sch'), # shch + (u'щ', u'sch'), # sch (u'ю', u'u'), # iu / yu ] CYRILLIC = add_uppercase_char(_CYRILLIC) diff --git a/test.py b/test.py index ddf1bf4..752c499 100644 --- a/test.py +++ b/test.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- import io -import os import sys import unittest from contextlib import contextmanager @@ -10,7 +9,7 @@ from slugify.__main__ import slugify_params, parse_args -class TestSlugification(unittest.TestCase): +class TestSlugify(unittest.TestCase): def test_extraneous_seperators(self): diff --git a/tox.ini b/tox.ini deleted file mode 100644 index a4bee82..0000000 --- a/tox.ini +++ /dev/null @@ -1,18 +0,0 @@ -[tox] -envlist = py{39,38,37,36},pypy3 - -[testenv] -deps= - -e . -commands = - python -m unittest test - -[testenv:format] -deps = pycodestyle -allowlist_externals = sh -commands = sh format.sh - -[testenv:coverage] -deps = coverage -commands = - coverage run --source=slugify test.py From b2aaccd0b0e8ac4780ff29221d7d0fe915aa3f0d Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 17:26:48 -0500 Subject: [PATCH 077/119] add staging to ci --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4998a5..4148354 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,7 @@ on: push: branches: - ci + - staging jobs: build: From dce218991ddf16d916501fb6f72980f9ca0c892a Mon Sep 17 00:00:00 2001 From: Reza Moradi Date: Wed, 16 Feb 2022 23:36:00 +0100 Subject: [PATCH 078/119] Fix misleading pattern name and documentation (#109) * Add better typing for slugify.slugify Currently, mypy understands the type as `Iterable[str]`, which doesn't match what should actually be passed in, which is `Iterable[Iterable[str]]` or, ideally, `Iterable[Tuple[str, str]]` * whitespace around = * fix misleading pattern name and documentation * fix README.md and cli doc as well Co-authored-by: Fahrzin Hemmati Co-authored-by: Val Neekman (AvidCoder) --- README.md | 2 +- slugify/__main__.py | 2 +- slugify/slugify.py | 15 ++++++--------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index af10080..11e20da 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ def slugify( :param save_order (bool): if parameter is True and max_length > 0 return whole words in the initial order :param separator (str): separator between words :param stopwords (iterable): words to discount - :param regex_pattern (str): regex pattern for allowed characters + :param regex_pattern (str): regex pattern for disallowed characters :param lowercase (bool): activate case sensitivity by setting it to False :param replacements (iterable): list of replacement rules e.g. [['|', 'or'], ['%', 'percent']] :return (str): slugify text diff --git a/slugify/__main__.py b/slugify/__main__.py index ffdfd5f..5a888fe 100644 --- a/slugify/__main__.py +++ b/slugify/__main__.py @@ -31,7 +31,7 @@ def parse_args(argv): parser.add_argument("--stopwords", nargs='+', help="Words to discount") parser.add_argument("--regex-pattern", - help="Python regex pattern for allowed characters") + help="Python regex pattern for disallowed characters") parser.add_argument("--no-lowercase", action='store_false', dest='lowercase', default=True, help="Activate case sensitivity") parser.add_argument("--replacements", nargs='+', diff --git a/slugify/slugify.py b/slugify/slugify.py index f38df10..190ea92 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -1,6 +1,7 @@ import re -import unicodedata import sys +import typing +import unicodedata from html.entities import name2codepoint try: @@ -15,8 +16,7 @@ DECIMAL_PATTERN = re.compile(r'&#(\d+);') HEX_PATTERN = re.compile(r'&#x([\da-fA-F]+);') QUOTE_PATTERN = re.compile(r'[\']+') -ALLOWED_CHARS_PATTERN = re.compile(r'[^-a-z0-9]+') -ALLOWED_CHARS_PATTERN_WITH_UPPERCASE = re.compile(r'[^-a-zA-Z0-9]+') +DISALLOWED_CHARS_PATTERN = re.compile(r'[^-a-zA-Z0-9]+') DUPLICATE_DASH_PATTERN = re.compile(r'-{2,}') NUMBERS_PATTERN = re.compile(r'(?<=\d),(?=\d)') DEFAULT_SEPARATOR = '-' @@ -66,7 +66,7 @@ def smart_truncate(string, max_length=0, word_boundary=False, separator=' ', sav def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, word_boundary=False, separator=DEFAULT_SEPARATOR, save_order=False, stopwords=(), regex_pattern=None, lowercase=True, - replacements=()): + replacements: typing.Iterable[typing.Iterable[str]] = ()): """ Make a slug from the given text. :param text (str): initial text @@ -78,7 +78,7 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w :param save_order (bool): if parameter is True and max_length > 0 return whole words in the initial order :param separator (str): separator between words :param stopwords (iterable): words to discount - :param regex_pattern (str): regex pattern for allowed characters + :param regex_pattern (str): regex pattern for disallowed characters :param lowercase (bool): activate case sensitivity by setting it to False :param replacements (iterable): list of replacement rules e.g. [['|', 'or'], ['%', 'percent']] :return (str): @@ -137,10 +137,7 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w text = NUMBERS_PATTERN.sub('', text) # replace all other unwanted characters - if lowercase: - pattern = regex_pattern or ALLOWED_CHARS_PATTERN - else: - pattern = regex_pattern or ALLOWED_CHARS_PATTERN_WITH_UPPERCASE + pattern = regex_pattern or DISALLOWED_CHARS_PATTERN text = re.sub(pattern, DEFAULT_SEPARATOR, text) # remove redundant From d8c9d8a1220743f28f98de60a3eed9cbae30c624 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 17:41:14 -0500 Subject: [PATCH 079/119] regex_patter to disallow --- CHANGELOG.md | 5 +++++ slugify/__version__.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8053b87..95ad243 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 6.0.1 + +- Rework regex_pattern to mean the opposite (disallowed chars instead of allowed) +- Thanks to @yyyyyyyan for the initial PR followed by the final PR by @mrezzamoradi + ## 6.0.0 - Enable github action diff --git a/slugify/__version__.py b/slugify/__version__.py index 4e5471b..1eedf44 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -5,4 +5,4 @@ __url__ = 'https://github.com/un33k/python-slugify' __license__ = 'MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' -__version__ = '6.0.0' +__version__ = '6.0.1' From c096bcdd76b0ef216716eccef5b19839266e1372 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 16 Feb 2022 17:46:24 -0500 Subject: [PATCH 080/119] regex: allow=>disallow --- .github/workflows/ci.yml | 1 + CHANGELOG.md | 5 +++++ README.md | 2 +- slugify/__main__.py | 2 +- slugify/__version__.py | 2 +- slugify/slugify.py | 15 ++++++--------- 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4998a5..4148354 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,7 @@ on: push: branches: - ci + - staging jobs: build: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8053b87..95ad243 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 6.0.1 + +- Rework regex_pattern to mean the opposite (disallowed chars instead of allowed) +- Thanks to @yyyyyyyan for the initial PR followed by the final PR by @mrezzamoradi + ## 6.0.0 - Enable github action diff --git a/README.md b/README.md index af10080..11e20da 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ def slugify( :param save_order (bool): if parameter is True and max_length > 0 return whole words in the initial order :param separator (str): separator between words :param stopwords (iterable): words to discount - :param regex_pattern (str): regex pattern for allowed characters + :param regex_pattern (str): regex pattern for disallowed characters :param lowercase (bool): activate case sensitivity by setting it to False :param replacements (iterable): list of replacement rules e.g. [['|', 'or'], ['%', 'percent']] :return (str): slugify text diff --git a/slugify/__main__.py b/slugify/__main__.py index ffdfd5f..5a888fe 100644 --- a/slugify/__main__.py +++ b/slugify/__main__.py @@ -31,7 +31,7 @@ def parse_args(argv): parser.add_argument("--stopwords", nargs='+', help="Words to discount") parser.add_argument("--regex-pattern", - help="Python regex pattern for allowed characters") + help="Python regex pattern for disallowed characters") parser.add_argument("--no-lowercase", action='store_false', dest='lowercase', default=True, help="Activate case sensitivity") parser.add_argument("--replacements", nargs='+', diff --git a/slugify/__version__.py b/slugify/__version__.py index 4e5471b..1eedf44 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -5,4 +5,4 @@ __url__ = 'https://github.com/un33k/python-slugify' __license__ = 'MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' -__version__ = '6.0.0' +__version__ = '6.0.1' diff --git a/slugify/slugify.py b/slugify/slugify.py index f38df10..190ea92 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -1,6 +1,7 @@ import re -import unicodedata import sys +import typing +import unicodedata from html.entities import name2codepoint try: @@ -15,8 +16,7 @@ DECIMAL_PATTERN = re.compile(r'&#(\d+);') HEX_PATTERN = re.compile(r'&#x([\da-fA-F]+);') QUOTE_PATTERN = re.compile(r'[\']+') -ALLOWED_CHARS_PATTERN = re.compile(r'[^-a-z0-9]+') -ALLOWED_CHARS_PATTERN_WITH_UPPERCASE = re.compile(r'[^-a-zA-Z0-9]+') +DISALLOWED_CHARS_PATTERN = re.compile(r'[^-a-zA-Z0-9]+') DUPLICATE_DASH_PATTERN = re.compile(r'-{2,}') NUMBERS_PATTERN = re.compile(r'(?<=\d),(?=\d)') DEFAULT_SEPARATOR = '-' @@ -66,7 +66,7 @@ def smart_truncate(string, max_length=0, word_boundary=False, separator=' ', sav def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, word_boundary=False, separator=DEFAULT_SEPARATOR, save_order=False, stopwords=(), regex_pattern=None, lowercase=True, - replacements=()): + replacements: typing.Iterable[typing.Iterable[str]] = ()): """ Make a slug from the given text. :param text (str): initial text @@ -78,7 +78,7 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w :param save_order (bool): if parameter is True and max_length > 0 return whole words in the initial order :param separator (str): separator between words :param stopwords (iterable): words to discount - :param regex_pattern (str): regex pattern for allowed characters + :param regex_pattern (str): regex pattern for disallowed characters :param lowercase (bool): activate case sensitivity by setting it to False :param replacements (iterable): list of replacement rules e.g. [['|', 'or'], ['%', 'percent']] :return (str): @@ -137,10 +137,7 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w text = NUMBERS_PATTERN.sub('', text) # replace all other unwanted characters - if lowercase: - pattern = regex_pattern or ALLOWED_CHARS_PATTERN - else: - pattern = regex_pattern or ALLOWED_CHARS_PATTERN_WITH_UPPERCASE + pattern = regex_pattern or DISALLOWED_CHARS_PATTERN text = re.sub(pattern, DEFAULT_SEPARATOR, text) # remove redundant From d968ca7419e6f4e40685888c56d03bea50fd39d7 Mon Sep 17 00:00:00 2001 From: Reza Moradi Date: Tue, 22 Feb 2022 20:05:08 +0100 Subject: [PATCH 081/119] allow unicode (#111) * initial commit to allow unicode * update version and changelog * add the flag to the CLI * update README.md --- CHANGELOG.md | 4 + README.md | 16 ++- slugify/__main__.py | 5 +- slugify/__version__.py | 2 +- slugify/slugify.py | 20 ++- test.py | 288 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 328 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95ad243..49f88dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.1.0 + +- Add `allow_unicode` flag to allow unicode characters in the slug + ## 6.0.1 - Rework regex_pattern to mean the opposite (disallowed chars instead of allowed) diff --git a/README.md b/README.md index 11e20da..f93afee 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,8 @@ def slugify( stopwords=(), regex_pattern=None, lowercase=True, - replacements=() + replacements=(), + allow_unicode=False ): """ Make a slug from the given text. @@ -58,6 +59,7 @@ def slugify( :param regex_pattern (str): regex pattern for disallowed characters :param lowercase (bool): activate case sensitivity by setting it to False :param replacements (iterable): list of replacement rules e.g. [['|', 'or'], ['%', 'percent']] + :param allow_unicode (bool): allow unicode characters :return (str): slugify text """ ``` @@ -75,6 +77,10 @@ txt = '影師嗎' r = slugify(txt) self.assertEqual(r, "ying-shi-ma") +txt = '影師嗎' +r = slugify(txt, allow_unicode=True) +self.assertEqual(r, "影師嗎") + txt = 'C\'est déjà l\'été.' r = slugify(txt) self.assertEqual(r, "c-est-deja-l-ete") @@ -133,6 +139,14 @@ txt = 'ÜBER Über German Umlaut' r = slugify(txt, replacements=[['Ü', 'UE'], ['ü', 'ue']]) self.assertEqual(r, "ueber-ueber-german-umlaut") +txt = 'i love 🦄' +r = slugify(txt, allow_unicode=True) +self.assertEqual(r, "i-love") + +txt = 'i love 🦄' +r = slugify(txt, allow_unicode=True, regex_pattern=r'[^🦄]+') +self.assertEqual(r, "🦄") + ``` For more examples, have a look at the [test.py](test.py) file. diff --git a/slugify/__main__.py b/slugify/__main__.py index 5a888fe..7dd6b01 100644 --- a/slugify/__main__.py +++ b/slugify/__main__.py @@ -36,6 +36,8 @@ def parse_args(argv): help="Activate case sensitivity") parser.add_argument("--replacements", nargs='+', help="""Additional replacement rules e.g. "|->or", "%%->percent".""") + parser.add_argument("--allow-unicode", action='store_true', default=False, + help="Allow unicode characters") args = parser.parse_args(argv[1:]) @@ -73,7 +75,8 @@ def slugify_params(args): separator=args.separator, stopwords=args.stopwords, lowercase=args.lowercase, - replacements=args.replacements + replacements=args.replacements, + allow_unicode=args.allow_unicode ) diff --git a/slugify/__version__.py b/slugify/__version__.py index 1eedf44..e14e887 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -5,4 +5,4 @@ __url__ = 'https://github.com/un33k/python-slugify' __license__ = 'MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' -__version__ = '6.0.1' +__version__ = '6.1.0' diff --git a/slugify/slugify.py b/slugify/slugify.py index 190ea92..ae6c9b6 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -17,6 +17,7 @@ HEX_PATTERN = re.compile(r'&#x([\da-fA-F]+);') QUOTE_PATTERN = re.compile(r'[\']+') DISALLOWED_CHARS_PATTERN = re.compile(r'[^-a-zA-Z0-9]+') +DISALLOWED_UNICODE_CHARS_PATTERN = re.compile(r'[\W_]+') DUPLICATE_DASH_PATTERN = re.compile(r'-{2,}') NUMBERS_PATTERN = re.compile(r'(?<=\d),(?=\d)') DEFAULT_SEPARATOR = '-' @@ -66,7 +67,8 @@ def smart_truncate(string, max_length=0, word_boundary=False, separator=' ', sav def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, word_boundary=False, separator=DEFAULT_SEPARATOR, save_order=False, stopwords=(), regex_pattern=None, lowercase=True, - replacements: typing.Iterable[typing.Iterable[str]] = ()): + replacements: typing.Iterable[typing.Iterable[str]] = (), + allow_unicode=False): """ Make a slug from the given text. :param text (str): initial text @@ -81,6 +83,7 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w :param regex_pattern (str): regex pattern for disallowed characters :param lowercase (bool): activate case sensitivity by setting it to False :param replacements (iterable): list of replacement rules e.g. [['|', 'or'], ['%', 'percent']] + :param allow_unicode (bool): allow unicode characters :return (str): """ @@ -97,7 +100,8 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w text = QUOTE_PATTERN.sub(DEFAULT_SEPARATOR, text) # decode unicode - text = unidecode.unidecode(text) + if not allow_unicode: + text = unidecode.unidecode(text) # ensure text is still in unicode if not isinstance(text, str): @@ -122,7 +126,11 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w pass # translate - text = unicodedata.normalize('NFKD', text) + if allow_unicode: + text = unicodedata.normalize('NFKC', text) + else: + text = unicodedata.normalize('NFKD', text) + if sys.version_info < (3,): text = text.encode('ascii', 'ignore') @@ -137,7 +145,11 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w text = NUMBERS_PATTERN.sub('', text) # replace all other unwanted characters - pattern = regex_pattern or DISALLOWED_CHARS_PATTERN + if allow_unicode: + pattern = regex_pattern or DISALLOWED_UNICODE_CHARS_PATTERN + else: + pattern = regex_pattern or DISALLOWED_CHARS_PATTERN + text = re.sub(pattern, DEFAULT_SEPARATOR, text) # remove redundant diff --git a/test.py b/test.py index 752c499..931f38f 100644 --- a/test.py +++ b/test.py @@ -233,6 +233,294 @@ def test_replacements_german_umlaut_custom(self): self.assertEqual(r, "ueber-ueber-german-umlaut") +class TestSlugifyUnicode(unittest.TestCase): + + def test_extraneous_seperators(self): + + txt = "This is a test ---" + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "this-is-a-test") + + txt = "___This is a test ---" + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "this-is-a-test") + + txt = "___This is a test___" + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "this-is-a-test") + + def test_non_word_characters(self): + txt = "This -- is a ## test ---" + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "this-is-a-test") + + def test_phonetic_conversion_of_eastern_scripts(self): + txt = '影師嗎' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, txt) + + def test_accented_text(self): + txt = 'C\'est déjà l\'été.' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "c-est-déjà-l-été") + + txt = 'Nín hǎo. Wǒ shì zhōng guó rén' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "nín-hǎo-wǒ-shì-zhōng-guó-rén") + + def test_accented_text_with_non_word_characters(self): + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "jaja-lol-méméméoo-a") + + def test_cyrillic_text(self): + txt = 'Компьютер' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "компьютер") + + def test_max_length(self): + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=9) + self.assertEqual(r, "jaja-lol") + + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=15) + self.assertEqual(r, "jaja-lol-mémémé") + + def test_max_length_cutoff_not_required(self): + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=50) + self.assertEqual(r, "jaja-lol-méméméoo-a") + + def test_word_boundary(self): + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=15, word_boundary=True) + self.assertEqual(r, "jaja-lol-a") + + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=17, word_boundary=True) + self.assertEqual(r, "jaja-lol-méméméoo") + + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=18, word_boundary=True) + self.assertEqual(r, "jaja-lol-méméméoo") + + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=19, word_boundary=True) + self.assertEqual(r, "jaja-lol-méméméoo-a") + + def test_custom_separator(self): + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=20, word_boundary=True, separator=".") + self.assertEqual(r, "jaja.lol.méméméoo.a") + + def test_multi_character_separator(self): + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=20, word_boundary=True, separator="ZZZZZZ") + self.assertEqual(r, "jajaZZZZZZlolZZZZZZméméméooZZZZZZa") + + def test_save_order(self): + txt = 'one two three four five' + r = slugify(txt, allow_unicode=True, max_length=13, word_boundary=True, save_order=True) + self.assertEqual(r, "one-two-three") + + txt = 'one two three four five' + r = slugify(txt, allow_unicode=True, max_length=13, word_boundary=True, save_order=False) + self.assertEqual(r, "one-two-three") + + txt = 'one two three four five' + r = slugify(txt, allow_unicode=True, max_length=12, word_boundary=True, save_order=False) + self.assertEqual(r, "one-two-four") + + txt = 'one two three four five' + r = slugify(txt, allow_unicode=True, max_length=12, word_boundary=True, save_order=True) + self.assertEqual(r, "one-two") + + def test_save_order_rtl(self): + """For right-to-left unicode languages""" + txt = 'دو سه چهار پنج' + r = slugify(txt, allow_unicode=True, max_length=10, word_boundary=True, save_order=True) + self.assertEqual(r, "دو-سه-چهار") + + txt = 'دو سه چهار پنج' + r = slugify(txt, allow_unicode=True, max_length=10, word_boundary=True, save_order=False) + self.assertEqual(r, "دو-سه-چهار") + + txt = 'دو سه چهار پنج' + r = slugify(txt, allow_unicode=True, max_length=9, word_boundary=True, save_order=False) + self.assertEqual(r, "دو-سه-پنج") + + txt = 'دو سه چهار پنج' + r = slugify(txt, allow_unicode=True, max_length=9, word_boundary=True, save_order=True) + self.assertEqual(r, "دو-سه") + + def test_stopword_removal(self): + txt = 'this has a stopword' + r = slugify(txt, allow_unicode=True, stopwords=['stopword']) + self.assertEqual(r, 'this-has-a') + + txt = 'this has a Öländ' + r = slugify(txt, allow_unicode=True, stopwords=['Öländ']) + self.assertEqual(r, 'this-has-a') + + def test_stopword_removal_casesensitive(self): + txt = 'thIs Has a stopword Stopword' + r = slugify(txt, allow_unicode=True, stopwords=['Stopword'], lowercase=False) + self.assertEqual(r, 'thIs-Has-a-stopword') + + txt = 'thIs Has a öländ Öländ' + r = slugify(txt, allow_unicode=True, stopwords=['Öländ'], lowercase=False) + self.assertEqual(r, 'thIs-Has-a-öländ') + + def test_multiple_stopword_occurances(self): + txt = 'the quick brown fox jumps over the lazy dog' + r = slugify(txt, allow_unicode=True, stopwords=['the']) + self.assertEqual(r, 'quick-brown-fox-jumps-over-lazy-dog') + + def test_differently_cased_stopword_match(self): + txt = 'Foo A FOO B foo C' + r = slugify(txt, allow_unicode=True, stopwords=['foo']) + self.assertEqual(r, 'a-b-c') + + txt = 'Foo A FOO B foo C' + r = slugify(txt, allow_unicode=True, stopwords=['FOO']) + self.assertEqual(r, 'a-b-c') + + def test_multiple_stopwords(self): + txt = 'the quick brown fox jumps over the lazy dog in a hurry' + r = slugify(txt, allow_unicode=True, stopwords=['the', 'in', 'a', 'hurry']) + self.assertEqual(r, 'quick-brown-fox-jumps-over-lazy-dog') + + def test_stopwords_with_different_separator(self): + txt = 'the quick brown fox jumps over the lazy dog' + r = slugify(txt, allow_unicode=True, stopwords=['the'], separator=' ') + self.assertEqual(r, 'quick brown fox jumps over lazy dog') + + def test_html_entities_on(self): + txt = 'foo & bar' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, 'foo-bar') + + def test_html_entities_off(self): + txt = 'foo & bår' + r = slugify(txt, allow_unicode=True, entities=False) + self.assertEqual(r, 'foo-amp-bår') + + def test_html_decimal_on(self): + txt = 'Ž' + r = slugify(txt, allow_unicode=True, decimal=True) + self.assertEqual(r, 'ž') + + def test_html_decimal_off(self): + txt = 'Ž' + r = slugify(txt, allow_unicode=True, entities=False, decimal=False) + self.assertEqual(r, '381') + + def test_html_hexadecimal_on(self): + txt = 'Ž' + r = slugify(txt, allow_unicode=True, hexadecimal=True) + self.assertEqual(r, 'ž') + + def test_html_hexadecimal_off(self): + txt = 'Ž' + r = slugify(txt, allow_unicode=True, hexadecimal=False) + self.assertEqual(r, 'x17d') + + def test_starts_with_number(self): + txt = '10 amazing secrets' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, '10-amazing-secrets') + + def test_contains_numbers(self): + txt = 'buildings with 1000 windows' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, 'buildings-with-1000-windows') + + def test_ends_with_number(self): + txt = 'recipe number 3' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, 'recipe-number-3') + + def test_numbers_only(self): + txt = '404' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, '404') + + def test_numbers_and_symbols(self): + txt = '1,000 reasons you are #1' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, '1000-reasons-you-are-1') + + txt = '۱,۰۰۰ reasons you are #۱' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, '۱۰۰۰-reasons-you-are-۱') + + def test_regex_pattern_keep_underscore(self): + """allowing unicode should not overrule the passed regex_pattern""" + txt = "___This is a test___" + regex_pattern = r'[^-a-z0-9_]+' + r = slugify(txt, allow_unicode=True, regex_pattern=regex_pattern) + self.assertEqual(r, "___this-is-a-test___") + + def test_regex_pattern_keep_underscore_with_underscore_as_separator(self): + """ + The regex_pattern turns the power to the caller. + Hence, the caller must ensure that a custom separator doesn't clash + with the regex_pattern. + """ + txt = "___This is a test___" + regex_pattern = r'[^-a-z0-9_]+' + r = slugify(txt, allow_unicode=True, separator='_', regex_pattern=regex_pattern) + self.assertNotEqual(r, "_this_is_a_test_") + + def test_replacements(self): + txt = '10 | 20 %' + r = slugify(txt, allow_unicode=True, replacements=[['|', 'or'], ['%', 'percent']]) + self.assertEqual(r, "10-or-20-percent") + + txt = 'I ♥ 🦄' + r = slugify(txt, allow_unicode=True, replacements=[['♥', 'amour'], ['🦄', 'licorne']]) + self.assertEqual(r, "i-amour-licorne") + + txt = 'I ♥ 🦄' + r = slugify(txt, allow_unicode=True, replacements=[['♥', 'სიყვარული'], ['🦄', 'licorne']]) + self.assertEqual(r, "i-სიყვარული-licorne") + + def test_replacements_german_umlaut_custom(self): + txt = 'ÜBER Über German Umlaut' + r = slugify(txt, allow_unicode=True, replacements=[['Ü', 'UE'], ['ü', 'ue']]) + self.assertEqual(r, "ueber-ueber-german-umlaut") + + def test_emojis(self): + """ + allowing unicode shouldn't allow emojis, even in replacements. + the only exception is when it is allowed by the regex_pattern. regex_pattern overrules all + """ + txt = 'i love 🦄' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "i-love") + + txt = 'i love 🦄' + r = slugify(txt, allow_unicode=True, decimal=True) + self.assertEqual(r, "i-love") + + txt = 'i love 🦄' + r = slugify(txt, allow_unicode=True, hexadecimal=True) + self.assertEqual(r, "i-love") + + txt = 'i love 🦄' + r = slugify(txt, allow_unicode=True, entities=True) + self.assertEqual(r, "i-love") + + txt = 'i love you' + r = slugify(txt, allow_unicode=True, replacements=[['you', '🦄']]) + self.assertEqual(r, "i-love") + + txt = 'i love 🦄' + r = slugify(txt, allow_unicode=True, regex_pattern=r'[^🦄]+') + self.assertEqual(r, "🦄") + + class TestUtils(unittest.TestCase): def test_smart_truncate_no_max_length(self): From 0bf1b8761f695e5ae14d9c439a05b151b4a1093d Mon Sep 17 00:00:00 2001 From: Reza Moradi Date: Sat, 26 Feb 2022 19:55:55 +0100 Subject: [PATCH 082/119] remove type hinting (#113) --- CHANGELOG.md | 4 ++++ slugify/__version__.py | 2 +- slugify/slugify.py | 4 +--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49f88dd..12d0ff1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.1.1 + +- Remove type hinting (temporarily) + ## 6.1.0 - Add `allow_unicode` flag to allow unicode characters in the slug diff --git a/slugify/__version__.py b/slugify/__version__.py index e14e887..f971770 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -5,4 +5,4 @@ __url__ = 'https://github.com/un33k/python-slugify' __license__ = 'MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' -__version__ = '6.1.0' +__version__ = '6.1.1' diff --git a/slugify/slugify.py b/slugify/slugify.py index ae6c9b6..b8c02ad 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -1,6 +1,5 @@ import re import sys -import typing import unicodedata from html.entities import name2codepoint @@ -67,8 +66,7 @@ def smart_truncate(string, max_length=0, word_boundary=False, separator=' ', sav def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, word_boundary=False, separator=DEFAULT_SEPARATOR, save_order=False, stopwords=(), regex_pattern=None, lowercase=True, - replacements: typing.Iterable[typing.Iterable[str]] = (), - allow_unicode=False): + replacements=(), allow_unicode=False): """ Make a slug from the given text. :param text (str): initial text From 442cca2b6ab88ccf32f5de5f73cfb17cd8248a5d Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 27 Apr 2022 11:59:04 -0400 Subject: [PATCH 083/119] Add missing cmdLine options --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8fdb214..c3c4b3b 100755 --- a/setup.py +++ b/setup.py @@ -80,5 +80,6 @@ def status(s): 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', - ] + ], + entry_points={'console_scripts': ['slugify=slugify.__main__:main']}, ) From 64b60d68e3028a759f0a89a865fddd6647c4896a Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 27 Apr 2022 12:00:42 -0400 Subject: [PATCH 084/119] Up Version --- CHANGELOG.md | 4 ++++ slugify/__version__.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12d0ff1..2ba0bb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.1.2 + +- Reintroduce the cli options + ## 6.1.1 - Remove type hinting (temporarily) diff --git a/slugify/__version__.py b/slugify/__version__.py index f971770..55abc97 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -5,4 +5,4 @@ __url__ = 'https://github.com/un33k/python-slugify' __license__ = 'MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' -__version__ = '6.1.1' +__version__ = '6.1.2' From 5ce9210079597a39d57bf9cbe406a3db23cac414 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 27 Apr 2022 12:06:26 -0400 Subject: [PATCH 085/119] update dev deps --- dev.requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev.requirements.txt b/dev.requirements.txt index 2b4e781..5f94d7b 100644 --- a/dev.requirements.txt +++ b/dev.requirements.txt @@ -1,3 +1,3 @@ -pycodestyle==2.7.0 +pycodestyle==2.8.0 twine==3.4.1 flake8==4.0.1 \ No newline at end of file From c094c8a50371d6da08b782424ace5eca20943c8b Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 27 Apr 2022 12:07:55 -0400 Subject: [PATCH 086/119] cmdline options --- .github/workflows/ci.yml | 44 ++++++ .github/workflows/dev.yml | 44 ++++++ .github/workflows/main.yml | 43 ++++++ .python-version | 5 - .travis.yml | 22 --- .vscode/settings.json | 3 +- CHANGELOG.md | 22 +++ MANIFEST.in | 3 +- README.md | 26 +++- dev.requirements.txt | 5 +- setup.py | 117 ++++++++------- slugify/__init__.py | 5 - slugify/__main__.py | 11 +- slugify/__version__.py | 8 + slugify/slugify.py | 55 ++++--- slugify/special.py | 2 +- test.py | 291 ++++++++++++++++++++++++++++++++++++- tox.ini | 18 --- 18 files changed, 574 insertions(+), 150 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/dev.yml create mode 100644 .github/workflows/main.yml delete mode 100644 .python-version delete mode 100644 .travis.yml create mode 100644 slugify/__version__.py delete mode 100644 tox.ini diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4148354 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +name: CI + +# Run on push only for dev/sandbox +# Otherwise it may trigger concurrently `push & pull_request` on PRs. +on: + push: + branches: + - ci + - staging + +jobs: + build: + name: Python ${{ matrix.python }} + runs-on: ubuntu-latest + strategy: + matrix: + python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + + steps: + - uses: actions/checkout@v2 + - name: setup python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install coveralls --upgrade + - name: Run flake8 + run: | + pip install flake8 --upgrade + flake8 --exclude=build --ignore=E501,F403,F401,E241,E225,E128 . + - name: Run pycodestyle + run: | + pip install pycodestyle --upgrade + pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py + - name: Run test + run: | + coverage run --source=slugify test.py + - name: Coveralls + run: coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml new file mode 100644 index 0000000..d0cb401 --- /dev/null +++ b/.github/workflows/dev.yml @@ -0,0 +1,44 @@ +name: DEV + +# Run on push only for dev/sandbox +# Otherwise it may trigger concurrently `push & pull_request` on PRs. +on: + push: + branches: + - sandbox + - dev + +jobs: + build: + name: Python ${{ matrix.python }} + runs-on: ubuntu-latest + strategy: + matrix: + python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + + steps: + - uses: actions/checkout@v2 + - name: setup python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install coveralls --upgrade + - name: Run flake8 + run: | + pip install flake8 --upgrade + flake8 --exclude=build --ignore=E501,F403,F401,E241,E225,E128 . + - name: Run pycodestyle + run: | + pip install pycodestyle --upgrade + pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py + - name: Run test + run: | + coverage run --source=slugify test.py + - name: Coveralls + run: coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..f1e75b7 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,43 @@ +name: Main + +# Run on push only for dev/sandbox +# Otherwise it may trigger concurrently `push & pull_request` on PRs. +on: + push: + branches: + - master + +jobs: + build: + name: Python ${{ matrix.python }} + runs-on: ubuntu-latest + strategy: + matrix: + python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + + steps: + - uses: actions/checkout@v2 + - name: setup python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install coveralls --upgrade + - name: Run flake8 + run: | + pip install flake8 --upgrade + flake8 --exclude=build --ignore=E501,F403,F401,E241,E225,E128 . + - name: Run pycodestyle + run: | + pip install pycodestyle --upgrade + pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py + - name: Run test + run: | + coverage run --source=slugify test.py + - name: Coveralls + run: coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.python-version b/.python-version deleted file mode 100644 index a8733ab..0000000 --- a/.python-version +++ /dev/null @@ -1,5 +0,0 @@ -3.9.2 -3.8.8 -3.7.10 -3.6.13 -pypy3.7-7.3.3 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2f8b3ce..0000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: python -dist: xenial - -python: - - "3.6" - - "3.7" - - "3.8" - - "3.9" - - "pypy3" - -install: - - pip install pip -U - - pip install -e . - - pip install pycodestyle - - pip install coveralls - -before_script: - - "bash format.sh" - -script: coverage run --source=slugify test.py - -after_success: coveralls diff --git a/.vscode/settings.json b/.vscode/settings.json index 2ab09c1..ecfbb80 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "python.linting.pylintEnabled": false, "python.pythonPath": "/usr/bin/python3", -} \ No newline at end of file + "cSpell.words": ["Neekman", "shch", "xlate"] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 777f6dc..2ba0bb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +## 6.1.2 + +- Reintroduce the cli options + +## 6.1.1 + +- Remove type hinting (temporarily) + +## 6.1.0 + +- Add `allow_unicode` flag to allow unicode characters in the slug + +## 6.0.1 + +- Rework regex_pattern to mean the opposite (disallowed chars instead of allowed) +- Thanks to @yyyyyyyan for the initial PR followed by the final PR by @mrezzamoradi + +## 6.0.0 + +- Enable github action +- Remove tox, as we run the test on github action, the end users can refer to those test + ## 5.0.2 - Enable twine publish diff --git a/MANIFEST.in b/MANIFEST.in index 0c78f18..373701c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,3 @@ -include CHANGELOG.md include LICENSE include README.md -include test.py +include CHANGELOG.md diff --git a/README.md b/README.md index 2305794..f93afee 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,8 @@ def slugify( stopwords=(), regex_pattern=None, lowercase=True, - replacements=() + replacements=(), + allow_unicode=False ): """ Make a slug from the given text. @@ -55,9 +56,10 @@ def slugify( :param save_order (bool): if parameter is True and max_length > 0 return whole words in the initial order :param separator (str): separator between words :param stopwords (iterable): words to discount - :param regex_pattern (str): regex pattern for allowed characters + :param regex_pattern (str): regex pattern for disallowed characters :param lowercase (bool): activate case sensitivity by setting it to False :param replacements (iterable): list of replacement rules e.g. [['|', 'or'], ['%', 'percent']] + :param allow_unicode (bool): allow unicode characters :return (str): slugify text """ ``` @@ -75,6 +77,10 @@ txt = '影師嗎' r = slugify(txt) self.assertEqual(r, "ying-shi-ma") +txt = '影師嗎' +r = slugify(txt, allow_unicode=True) +self.assertEqual(r, "影師嗎") + txt = 'C\'est déjà l\'été.' r = slugify(txt) self.assertEqual(r, "c-est-deja-l-ete") @@ -133,6 +139,14 @@ txt = 'ÜBER Über German Umlaut' r = slugify(txt, replacements=[['Ü', 'UE'], ['ü', 'ue']]) self.assertEqual(r, "ueber-ueber-german-umlaut") +txt = 'i love 🦄' +r = slugify(txt, allow_unicode=True) +self.assertEqual(r, "i-love") + +txt = 'i love 🦄' +r = slugify(txt, allow_unicode=True, regex_pattern=r'[^🦄]+') +self.assertEqual(r, "🦄") + ``` For more examples, have a look at the [test.py](test.py) file. @@ -164,10 +178,6 @@ quick-brown-fox-jumps-over-lazy-dog # Running the tests -To run the tests against all environments: - - tox - To run the tests against the current environment: python test.py @@ -188,8 +198,8 @@ X.Y.Z Version `MINOR` version -- when you add functionality in a backwards-compatible manner, and `PATCH` version -- when you make backwards-compatible bug fixes. -[status-image]: https://travis-ci.org/un33k/python-slugify.svg?branch=master -[status-link]: https://travis-ci.org/un33k/python-slugify +[status-image]: https://github.com/un33k/python-slugify/actions/workflows/ci.yml/badge.svg +[status-link]: https://github.com/un33k/python-slugify/actions/workflows/ci.yml [version-image]: https://img.shields.io/pypi/v/python-slugify.svg [version-link]: https://pypi.python.org/pypi/python-slugify [coverage-image]: https://coveralls.io/repos/un33k/python-slugify/badge.svg diff --git a/dev.requirements.txt b/dev.requirements.txt index 337aa36..5f94d7b 100644 --- a/dev.requirements.txt +++ b/dev.requirements.txt @@ -1,2 +1,3 @@ -pycodestyle==2.7.0 -twine==3.4.1 \ No newline at end of file +pycodestyle==2.8.0 +twine==3.4.1 +flake8==4.0.1 \ No newline at end of file diff --git a/setup.py b/setup.py index 51b267f..c3c4b3b 100755 --- a/setup.py +++ b/setup.py @@ -1,70 +1,85 @@ #!/usr/bin/env python - -# -*- coding: utf-8 -*- -from setuptools import setup, find_packages -import re +# Learn more: https://github.com/un33k/setup.py import os import sys -import codecs -name = 'python-slugify' +from codecs import open +from shutil import rmtree +from setuptools import setup + + package = 'slugify' -description = 'A Python Slugify application that handles Unicode' -url = 'https://github.com/un33k/python-slugify' -author = 'Val Neekman' -author_email = 'info@neekware.com' -license = 'MIT' +python_requires = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +here = os.path.abspath(os.path.dirname(__file__)) + install_requires = ['text-unidecode>=1.3'] -extras_require = {'unidecode': ['Unidecode>=1.1.1']} +extras_requires = {'unidecode': ['Unidecode>=1.1.1']} +test_requires = [] -classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Build Tools', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', -] +about = {} +with open(os.path.join(here, package, '__version__.py'), 'r', 'utf-8') as f: + exec(f.read(), about) +with open('README.md', 'r', 'utf-8') as f: + readme = f.read() -def get_version(package): - """ - Return package version as listed in `__version__` in `init.py`. - """ - init_py = codecs.open(os.path.join(package, '__init__.py'), encoding='utf-8').read() - return re.search("^__version__ = ['\"]([^'\"]+)['\"]", init_py, re.MULTILINE).group(1) +def status(s): + print('\033[1m{0}\033[0m'.format(s)) -if sys.argv[-1] == 'build': - os.system("python setup.py sdist bdist_wheel") +# 'setup.py publish' shortcut. if sys.argv[-1] == 'publish': - os.system("python setup.py build && twine upload dist/*") - args = {'version': get_version(package)} - print("You probably want to also tag the version now:") - print(" git tag -a %(version)s -m 'version %(version)s' && git push --tags" % args) - sys.exit() + try: + status('Removing previous builds…') + rmtree(os.path.join(here, 'dist')) + except OSError: + pass + + status('Building Source and Wheel (universal) distribution…') + os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable)) -EXCLUDE_FROM_PACKAGES = [] + status('Uploading the package to PyPI via Twine…') + os.system('twine upload dist/*') + + status('Pushing git tags…') + os.system('git tag v{0}'.format(about['__version__'])) + os.system('git push --tags') + sys.exit() setup( - name=name, - version=get_version(package), - url=url, - license=license, - description=description, - long_description=description, - author=author, - author_email=author_email, - packages=find_packages(exclude=EXCLUDE_FROM_PACKAGES), + name=about['__title__'], + version=about['__version__'], + description=about['__description__'], + long_description=readme, + long_description_content_type='text/markdown', + author=about['__author__'], + author_email=about['__author_email__'], + url=about['__url__'], + license=about['__license__'], + packages=[package], + package_data={'': ['LICENSE']}, + package_dir={'slugify': 'slugify'}, + include_package_data=True, + python_requires=python_requires, install_requires=install_requires, - extras_require=extras_require, - python_requires='>=3.6', - classifiers=classifiers, + tests_require=test_requires, + extras_require=extras_requires, + zip_safe=False, + cmdclass={}, + project_urls={}, + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Natural Language :: English', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + ], entry_points={'console_scripts': ['slugify=slugify.__main__:main']}, ) diff --git a/slugify/__init__.py b/slugify/__init__.py index 6c59f4e..ac21492 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -1,7 +1,2 @@ from .special import * from .slugify import * - - -__author__ = 'Val Neekman @ Neekware Inc. [@vneekman]' -__description__ = 'A Python slugify application that also handles Unicode' -__version__ = '5.0.2' diff --git a/slugify/__main__.py b/slugify/__main__.py index f815206..7dd6b01 100644 --- a/slugify/__main__.py +++ b/slugify/__main__.py @@ -31,11 +31,13 @@ def parse_args(argv): parser.add_argument("--stopwords", nargs='+', help="Words to discount") parser.add_argument("--regex-pattern", - help="Python regex pattern for allowed characters") + help="Python regex pattern for disallowed characters") parser.add_argument("--no-lowercase", action='store_false', dest='lowercase', default=True, help="Activate case sensitivity") parser.add_argument("--replacements", nargs='+', help="""Additional replacement rules e.g. "|->or", "%%->percent".""") + parser.add_argument("--allow-unicode", action='store_true', default=False, + help="Allow unicode characters") args = parser.parse_args(argv[1:]) @@ -73,11 +75,12 @@ def slugify_params(args): separator=args.separator, stopwords=args.stopwords, lowercase=args.lowercase, - replacements=args.replacements + replacements=args.replacements, + allow_unicode=args.allow_unicode ) -def main(argv=None): # pragma: no cover +def main(argv=None): # pragma: no cover """ Run this program """ if argv is None: argv = sys.argv @@ -89,5 +92,5 @@ def main(argv=None): # pragma: no cover sys.exit(-1) -if __name__ == '__main__': # pragma: no cover +if __name__ == '__main__': # pragma: no cover main() diff --git a/slugify/__version__.py b/slugify/__version__.py new file mode 100644 index 0000000..55abc97 --- /dev/null +++ b/slugify/__version__.py @@ -0,0 +1,8 @@ +__title__ = 'python-slugify' +__author__ = 'Val Neekman' +__author_email__ = 'info@neekware.com' +__description__ = 'A Python slugify application that also handles Unicode' +__url__ = 'https://github.com/un33k/python-slugify' +__license__ = 'MIT' +__copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' +__version__ = '6.1.2' diff --git a/slugify/slugify.py b/slugify/slugify.py index bb3aa95..b8c02ad 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -1,17 +1,7 @@ import re -import unicodedata -import types import sys - -try: - from htmlentitydefs import name2codepoint - _unicode = unicode - _unicode_type = types.UnicodeType -except ImportError: - from html.entities import name2codepoint - _unicode = str - _unicode_type = str - unichr = chr +import unicodedata +from html.entities import name2codepoint try: import text_unidecode as unidecode @@ -25,8 +15,8 @@ DECIMAL_PATTERN = re.compile(r'&#(\d+);') HEX_PATTERN = re.compile(r'&#x([\da-fA-F]+);') QUOTE_PATTERN = re.compile(r'[\']+') -ALLOWED_CHARS_PATTERN = re.compile(r'[^-a-z0-9]+') -ALLOWED_CHARS_PATTERN_WITH_UPPERCASE = re.compile(r'[^-a-zA-Z0-9]+') +DISALLOWED_CHARS_PATTERN = re.compile(r'[^-a-zA-Z0-9]+') +DISALLOWED_UNICODE_CHARS_PATTERN = re.compile(r'[\W_]+') DUPLICATE_DASH_PATTERN = re.compile(r'-{2,}') NUMBERS_PATTERN = re.compile(r'(?<=\d),(?=\d)') DEFAULT_SEPARATOR = '-' @@ -69,14 +59,14 @@ def smart_truncate(string, max_length=0, word_boundary=False, separator=' ', sav else: if save_order: break - if not truncated: # pragma: no cover + if not truncated: # pragma: no cover truncated = string[:max_length] return truncated.strip(separator) def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, word_boundary=False, separator=DEFAULT_SEPARATOR, save_order=False, stopwords=(), regex_pattern=None, lowercase=True, - replacements=()): + replacements=(), allow_unicode=False): """ Make a slug from the given text. :param text (str): initial text @@ -88,9 +78,10 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w :param save_order (bool): if parameter is True and max_length > 0 return whole words in the initial order :param separator (str): separator between words :param stopwords (iterable): words to discount - :param regex_pattern (str): regex pattern for allowed characters + :param regex_pattern (str): regex pattern for disallowed characters :param lowercase (bool): activate case sensitivity by setting it to False :param replacements (iterable): list of replacement rules e.g. [['|', 'or'], ['%', 'percent']] + :param allow_unicode (bool): allow unicode characters :return (str): """ @@ -100,39 +91,44 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w text = text.replace(old, new) # ensure text is unicode - if not isinstance(text, _unicode_type): - text = _unicode(text, 'utf-8', 'ignore') + if not isinstance(text, str): + text = str(text, 'utf-8', 'ignore') # replace quotes with dashes - pre-process text = QUOTE_PATTERN.sub(DEFAULT_SEPARATOR, text) # decode unicode - text = unidecode.unidecode(text) + if not allow_unicode: + text = unidecode.unidecode(text) # ensure text is still in unicode - if not isinstance(text, _unicode_type): - text = _unicode(text, 'utf-8', 'ignore') + if not isinstance(text, str): + text = str(text, 'utf-8', 'ignore') # character entity reference if entities: - text = CHAR_ENTITY_PATTERN.sub(lambda m: unichr(name2codepoint[m.group(1)]), text) + text = CHAR_ENTITY_PATTERN.sub(lambda m: chr(name2codepoint[m.group(1)]), text) # decimal character reference if decimal: try: - text = DECIMAL_PATTERN.sub(lambda m: unichr(int(m.group(1))), text) + text = DECIMAL_PATTERN.sub(lambda m: chr(int(m.group(1))), text) except Exception: pass # hexadecimal character reference if hexadecimal: try: - text = HEX_PATTERN.sub(lambda m: unichr(int(m.group(1), 16)), text) + text = HEX_PATTERN.sub(lambda m: chr(int(m.group(1), 16)), text) except Exception: pass # translate - text = unicodedata.normalize('NFKD', text) + if allow_unicode: + text = unicodedata.normalize('NFKC', text) + else: + text = unicodedata.normalize('NFKD', text) + if sys.version_info < (3,): text = text.encode('ascii', 'ignore') @@ -147,10 +143,11 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w text = NUMBERS_PATTERN.sub('', text) # replace all other unwanted characters - if lowercase: - pattern = regex_pattern or ALLOWED_CHARS_PATTERN + if allow_unicode: + pattern = regex_pattern or DISALLOWED_UNICODE_CHARS_PATTERN else: - pattern = regex_pattern or ALLOWED_CHARS_PATTERN_WITH_UPPERCASE + pattern = regex_pattern or DISALLOWED_CHARS_PATTERN + text = re.sub(pattern, DEFAULT_SEPARATOR, text) # remove redundant diff --git a/slugify/special.py b/slugify/special.py index d3478d5..54eb85c 100644 --- a/slugify/special.py +++ b/slugify/special.py @@ -20,7 +20,7 @@ def add_uppercase_char(char_list): (u'я', u'ya'), # ia (u'х', u'h'), # kh (u'у', u'y'), # u - (u'щ', u'sch'), # shch + (u'щ', u'sch'), # sch (u'ю', u'u'), # iu / yu ] CYRILLIC = add_uppercase_char(_CYRILLIC) diff --git a/test.py b/test.py index ddf1bf4..931f38f 100644 --- a/test.py +++ b/test.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- import io -import os import sys import unittest from contextlib import contextmanager @@ -10,7 +9,7 @@ from slugify.__main__ import slugify_params, parse_args -class TestSlugification(unittest.TestCase): +class TestSlugify(unittest.TestCase): def test_extraneous_seperators(self): @@ -234,6 +233,294 @@ def test_replacements_german_umlaut_custom(self): self.assertEqual(r, "ueber-ueber-german-umlaut") +class TestSlugifyUnicode(unittest.TestCase): + + def test_extraneous_seperators(self): + + txt = "This is a test ---" + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "this-is-a-test") + + txt = "___This is a test ---" + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "this-is-a-test") + + txt = "___This is a test___" + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "this-is-a-test") + + def test_non_word_characters(self): + txt = "This -- is a ## test ---" + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "this-is-a-test") + + def test_phonetic_conversion_of_eastern_scripts(self): + txt = '影師嗎' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, txt) + + def test_accented_text(self): + txt = 'C\'est déjà l\'été.' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "c-est-déjà-l-été") + + txt = 'Nín hǎo. Wǒ shì zhōng guó rén' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "nín-hǎo-wǒ-shì-zhōng-guó-rén") + + def test_accented_text_with_non_word_characters(self): + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "jaja-lol-méméméoo-a") + + def test_cyrillic_text(self): + txt = 'Компьютер' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "компьютер") + + def test_max_length(self): + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=9) + self.assertEqual(r, "jaja-lol") + + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=15) + self.assertEqual(r, "jaja-lol-mémémé") + + def test_max_length_cutoff_not_required(self): + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=50) + self.assertEqual(r, "jaja-lol-méméméoo-a") + + def test_word_boundary(self): + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=15, word_boundary=True) + self.assertEqual(r, "jaja-lol-a") + + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=17, word_boundary=True) + self.assertEqual(r, "jaja-lol-méméméoo") + + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=18, word_boundary=True) + self.assertEqual(r, "jaja-lol-méméméoo") + + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=19, word_boundary=True) + self.assertEqual(r, "jaja-lol-méméméoo-a") + + def test_custom_separator(self): + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=20, word_boundary=True, separator=".") + self.assertEqual(r, "jaja.lol.méméméoo.a") + + def test_multi_character_separator(self): + txt = 'jaja---lol-méméméoo--a' + r = slugify(txt, allow_unicode=True, max_length=20, word_boundary=True, separator="ZZZZZZ") + self.assertEqual(r, "jajaZZZZZZlolZZZZZZméméméooZZZZZZa") + + def test_save_order(self): + txt = 'one two three four five' + r = slugify(txt, allow_unicode=True, max_length=13, word_boundary=True, save_order=True) + self.assertEqual(r, "one-two-three") + + txt = 'one two three four five' + r = slugify(txt, allow_unicode=True, max_length=13, word_boundary=True, save_order=False) + self.assertEqual(r, "one-two-three") + + txt = 'one two three four five' + r = slugify(txt, allow_unicode=True, max_length=12, word_boundary=True, save_order=False) + self.assertEqual(r, "one-two-four") + + txt = 'one two three four five' + r = slugify(txt, allow_unicode=True, max_length=12, word_boundary=True, save_order=True) + self.assertEqual(r, "one-two") + + def test_save_order_rtl(self): + """For right-to-left unicode languages""" + txt = 'دو سه چهار پنج' + r = slugify(txt, allow_unicode=True, max_length=10, word_boundary=True, save_order=True) + self.assertEqual(r, "دو-سه-چهار") + + txt = 'دو سه چهار پنج' + r = slugify(txt, allow_unicode=True, max_length=10, word_boundary=True, save_order=False) + self.assertEqual(r, "دو-سه-چهار") + + txt = 'دو سه چهار پنج' + r = slugify(txt, allow_unicode=True, max_length=9, word_boundary=True, save_order=False) + self.assertEqual(r, "دو-سه-پنج") + + txt = 'دو سه چهار پنج' + r = slugify(txt, allow_unicode=True, max_length=9, word_boundary=True, save_order=True) + self.assertEqual(r, "دو-سه") + + def test_stopword_removal(self): + txt = 'this has a stopword' + r = slugify(txt, allow_unicode=True, stopwords=['stopword']) + self.assertEqual(r, 'this-has-a') + + txt = 'this has a Öländ' + r = slugify(txt, allow_unicode=True, stopwords=['Öländ']) + self.assertEqual(r, 'this-has-a') + + def test_stopword_removal_casesensitive(self): + txt = 'thIs Has a stopword Stopword' + r = slugify(txt, allow_unicode=True, stopwords=['Stopword'], lowercase=False) + self.assertEqual(r, 'thIs-Has-a-stopword') + + txt = 'thIs Has a öländ Öländ' + r = slugify(txt, allow_unicode=True, stopwords=['Öländ'], lowercase=False) + self.assertEqual(r, 'thIs-Has-a-öländ') + + def test_multiple_stopword_occurances(self): + txt = 'the quick brown fox jumps over the lazy dog' + r = slugify(txt, allow_unicode=True, stopwords=['the']) + self.assertEqual(r, 'quick-brown-fox-jumps-over-lazy-dog') + + def test_differently_cased_stopword_match(self): + txt = 'Foo A FOO B foo C' + r = slugify(txt, allow_unicode=True, stopwords=['foo']) + self.assertEqual(r, 'a-b-c') + + txt = 'Foo A FOO B foo C' + r = slugify(txt, allow_unicode=True, stopwords=['FOO']) + self.assertEqual(r, 'a-b-c') + + def test_multiple_stopwords(self): + txt = 'the quick brown fox jumps over the lazy dog in a hurry' + r = slugify(txt, allow_unicode=True, stopwords=['the', 'in', 'a', 'hurry']) + self.assertEqual(r, 'quick-brown-fox-jumps-over-lazy-dog') + + def test_stopwords_with_different_separator(self): + txt = 'the quick brown fox jumps over the lazy dog' + r = slugify(txt, allow_unicode=True, stopwords=['the'], separator=' ') + self.assertEqual(r, 'quick brown fox jumps over lazy dog') + + def test_html_entities_on(self): + txt = 'foo & bar' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, 'foo-bar') + + def test_html_entities_off(self): + txt = 'foo & bår' + r = slugify(txt, allow_unicode=True, entities=False) + self.assertEqual(r, 'foo-amp-bår') + + def test_html_decimal_on(self): + txt = 'Ž' + r = slugify(txt, allow_unicode=True, decimal=True) + self.assertEqual(r, 'ž') + + def test_html_decimal_off(self): + txt = 'Ž' + r = slugify(txt, allow_unicode=True, entities=False, decimal=False) + self.assertEqual(r, '381') + + def test_html_hexadecimal_on(self): + txt = 'Ž' + r = slugify(txt, allow_unicode=True, hexadecimal=True) + self.assertEqual(r, 'ž') + + def test_html_hexadecimal_off(self): + txt = 'Ž' + r = slugify(txt, allow_unicode=True, hexadecimal=False) + self.assertEqual(r, 'x17d') + + def test_starts_with_number(self): + txt = '10 amazing secrets' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, '10-amazing-secrets') + + def test_contains_numbers(self): + txt = 'buildings with 1000 windows' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, 'buildings-with-1000-windows') + + def test_ends_with_number(self): + txt = 'recipe number 3' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, 'recipe-number-3') + + def test_numbers_only(self): + txt = '404' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, '404') + + def test_numbers_and_symbols(self): + txt = '1,000 reasons you are #1' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, '1000-reasons-you-are-1') + + txt = '۱,۰۰۰ reasons you are #۱' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, '۱۰۰۰-reasons-you-are-۱') + + def test_regex_pattern_keep_underscore(self): + """allowing unicode should not overrule the passed regex_pattern""" + txt = "___This is a test___" + regex_pattern = r'[^-a-z0-9_]+' + r = slugify(txt, allow_unicode=True, regex_pattern=regex_pattern) + self.assertEqual(r, "___this-is-a-test___") + + def test_regex_pattern_keep_underscore_with_underscore_as_separator(self): + """ + The regex_pattern turns the power to the caller. + Hence, the caller must ensure that a custom separator doesn't clash + with the regex_pattern. + """ + txt = "___This is a test___" + regex_pattern = r'[^-a-z0-9_]+' + r = slugify(txt, allow_unicode=True, separator='_', regex_pattern=regex_pattern) + self.assertNotEqual(r, "_this_is_a_test_") + + def test_replacements(self): + txt = '10 | 20 %' + r = slugify(txt, allow_unicode=True, replacements=[['|', 'or'], ['%', 'percent']]) + self.assertEqual(r, "10-or-20-percent") + + txt = 'I ♥ 🦄' + r = slugify(txt, allow_unicode=True, replacements=[['♥', 'amour'], ['🦄', 'licorne']]) + self.assertEqual(r, "i-amour-licorne") + + txt = 'I ♥ 🦄' + r = slugify(txt, allow_unicode=True, replacements=[['♥', 'სიყვარული'], ['🦄', 'licorne']]) + self.assertEqual(r, "i-სიყვარული-licorne") + + def test_replacements_german_umlaut_custom(self): + txt = 'ÜBER Über German Umlaut' + r = slugify(txt, allow_unicode=True, replacements=[['Ü', 'UE'], ['ü', 'ue']]) + self.assertEqual(r, "ueber-ueber-german-umlaut") + + def test_emojis(self): + """ + allowing unicode shouldn't allow emojis, even in replacements. + the only exception is when it is allowed by the regex_pattern. regex_pattern overrules all + """ + txt = 'i love 🦄' + r = slugify(txt, allow_unicode=True) + self.assertEqual(r, "i-love") + + txt = 'i love 🦄' + r = slugify(txt, allow_unicode=True, decimal=True) + self.assertEqual(r, "i-love") + + txt = 'i love 🦄' + r = slugify(txt, allow_unicode=True, hexadecimal=True) + self.assertEqual(r, "i-love") + + txt = 'i love 🦄' + r = slugify(txt, allow_unicode=True, entities=True) + self.assertEqual(r, "i-love") + + txt = 'i love you' + r = slugify(txt, allow_unicode=True, replacements=[['you', '🦄']]) + self.assertEqual(r, "i-love") + + txt = 'i love 🦄' + r = slugify(txt, allow_unicode=True, regex_pattern=r'[^🦄]+') + self.assertEqual(r, "🦄") + + class TestUtils(unittest.TestCase): def test_smart_truncate_no_max_length(self): diff --git a/tox.ini b/tox.ini deleted file mode 100644 index a4bee82..0000000 --- a/tox.ini +++ /dev/null @@ -1,18 +0,0 @@ -[tox] -envlist = py{39,38,37,36},pypy3 - -[testenv] -deps= - -e . -commands = - python -m unittest test - -[testenv:format] -deps = pycodestyle -allowlist_externals = sh -commands = sh format.sh - -[testenv:coverage] -deps = coverage -commands = - coverage run --source=slugify test.py From 3f1a0fe7c5775a72141163ccdd593272e512898c Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Wed, 27 Apr 2022 12:08:35 -0400 Subject: [PATCH 087/119] Add cmdLine option --- CHANGELOG.md | 4 ++++ dev.requirements.txt | 2 +- setup.py | 3 ++- slugify/__version__.py | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12d0ff1..2ba0bb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.1.2 + +- Reintroduce the cli options + ## 6.1.1 - Remove type hinting (temporarily) diff --git a/dev.requirements.txt b/dev.requirements.txt index 2b4e781..5f94d7b 100644 --- a/dev.requirements.txt +++ b/dev.requirements.txt @@ -1,3 +1,3 @@ -pycodestyle==2.7.0 +pycodestyle==2.8.0 twine==3.4.1 flake8==4.0.1 \ No newline at end of file diff --git a/setup.py b/setup.py index 8fdb214..c3c4b3b 100755 --- a/setup.py +++ b/setup.py @@ -80,5 +80,6 @@ def status(s): 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', - ] + ], + entry_points={'console_scripts': ['slugify=slugify.__main__:main']}, ) diff --git a/slugify/__version__.py b/slugify/__version__.py index f971770..55abc97 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -5,4 +5,4 @@ __url__ = 'https://github.com/un33k/python-slugify' __license__ = 'MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' -__version__ = '6.1.1' +__version__ = '6.1.2' From dbe74897764eb8338151e09cfc611e90cef12280 Mon Sep 17 00:00:00 2001 From: eNV25 Date: Tue, 27 Sep 2022 17:28:23 +0530 Subject: [PATCH 088/119] Update __init__.py to have __verstion__ variables. (#116) This allows you to use `from slugify import __version__ as slugifyVersion`, while `from slugify.__version__ import __version__ as slugifyVersion` continues to work. --- slugify/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/slugify/__init__.py b/slugify/__init__.py index ac21492..6d3279f 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -1,2 +1,10 @@ from .special import * from .slugify import * +from .__version__ import __title__ +from .__version__ import __author__ +from .__version__ import __author_email__ +from .__version__ import __description__ +from .__version__ import __url__ +from .__version__ import __license__ +from .__version__ import __copyright__ +from .__version__ import __version__ From a229e483b6f82f36600e73e7a2fb62dc2f5e7ed3 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 18 Nov 2022 20:36:21 +0200 Subject: [PATCH 089/119] Add support for Python 3.11 (#121) * Add support for Python 3.11 * Drop support for EOL Python 3.6 --- .github/workflows/ci.yml | 6 +++--- .github/workflows/dev.yml | 6 +++--- .github/workflows/main.yml | 6 +++--- setup.cfg | 2 -- setup.py | 4 ++-- 5 files changed, 11 insertions(+), 13 deletions(-) delete mode 100644 setup.cfg diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4148354..56e4097 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,12 +14,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: setup python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - name: Install dependencies diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index d0cb401..73f2f07 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -14,12 +14,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: setup python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - name: Install dependencies diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f1e75b7..a4ae9ad 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,12 +13,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: setup python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - name: Install dependencies diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 3c6e79c..0000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -universal=1 diff --git a/setup.py b/setup.py index c3c4b3b..65c72f4 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ package = 'slugify' -python_requires = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python_requires = ">=3.7" here = os.path.abspath(os.path.dirname(__file__)) install_requires = ['text-unidecode>=1.3'] @@ -75,11 +75,11 @@ def status(s): 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', ], entry_points={'console_scripts': ['slugify=slugify.__main__:main']}, ) From c236c1589f3469881b809128021ab77719fa19fa Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Fri, 18 Nov 2022 13:47:14 -0500 Subject: [PATCH 090/119] github action revert --- .github/workflows/ci.yml | 2 +- .github/workflows/dev.yml | 2 +- .github/workflows/main.yml | 2 +- CHANGELOG.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da3899e..56e4097 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, 3.10, 3.11, pypy3.8] + python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 615e82a..73f2f07 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, 3.10, 3.11, pypy3.8] + python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 68d30fa..a4ae9ad 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, 3.10, 3.11, pypy3.8] + python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] steps: - uses: actions/checkout@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index b95783a..7e32d42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## 7.0.0 -- Drop python 3.6, add python 3.11 +- Drop python 3.6, add python 3.11 (@hugovk - thx) ## 6.1.2 From bc2399d85a79231602d90e20b803fc95f30f954d Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Fri, 18 Nov 2022 13:57:50 -0500 Subject: [PATCH 091/119] merge sandbox --- .github/workflows/ci.yml | 6 +++--- .github/workflows/dev.yml | 6 +++--- .github/workflows/main.yml | 6 +++--- CHANGELOG.md | 4 ++++ README.md | 1 + setup.cfg | 2 -- setup.py | 4 ++-- slugify/__init__.py | 8 ++++++++ slugify/__version__.py | 2 +- 9 files changed, 25 insertions(+), 14 deletions(-) delete mode 100644 setup.cfg diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4148354..56e4097 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,12 +14,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: setup python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - name: Install dependencies diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index d0cb401..73f2f07 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -14,12 +14,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: setup python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - name: Install dependencies diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f1e75b7..a4ae9ad 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,12 +13,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: setup python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - name: Install dependencies diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ba0bb3..7e32d42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 7.0.0 + +- Drop python 3.6, add python 3.11 (@hugovk - thx) + ## 6.1.2 - Reintroduce the cli options diff --git a/README.md b/README.md index f93afee..2514fc5 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ However, there is an alternative decoding package called [Unidecode](https://git - Python `2.7` <-> python-slugify `< 5.0.0` - Python `3.6+` <-> python-slugify `>= 5.0.0` +- Python `3.7+` <-> python-slugify `>= 7.0.0` # How to install diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 3c6e79c..0000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -universal=1 diff --git a/setup.py b/setup.py index c3c4b3b..65c72f4 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ package = 'slugify' -python_requires = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python_requires = ">=3.7" here = os.path.abspath(os.path.dirname(__file__)) install_requires = ['text-unidecode>=1.3'] @@ -75,11 +75,11 @@ def status(s): 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', ], entry_points={'console_scripts': ['slugify=slugify.__main__:main']}, ) diff --git a/slugify/__init__.py b/slugify/__init__.py index ac21492..6d3279f 100644 --- a/slugify/__init__.py +++ b/slugify/__init__.py @@ -1,2 +1,10 @@ from .special import * from .slugify import * +from .__version__ import __title__ +from .__version__ import __author__ +from .__version__ import __author_email__ +from .__version__ import __description__ +from .__version__ import __url__ +from .__version__ import __license__ +from .__version__ import __copyright__ +from .__version__ import __version__ diff --git a/slugify/__version__.py b/slugify/__version__.py index 55abc97..184339a 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -5,4 +5,4 @@ __url__ = 'https://github.com/un33k/python-slugify' __license__ = 'MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' -__version__ = '6.1.2' +__version__ = '7.0.0' From 247fe299c524f48adb94325ce79c84d87fbd0197 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Fri, 18 Nov 2022 14:03:48 -0500 Subject: [PATCH 092/119] drop py 3.6, add py 3.11, upversion --- CHANGELOG.md | 4 ++++ README.md | 1 + slugify/__version__.py | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ba0bb3..7e32d42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 7.0.0 + +- Drop python 3.6, add python 3.11 (@hugovk - thx) + ## 6.1.2 - Reintroduce the cli options diff --git a/README.md b/README.md index f93afee..2514fc5 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ However, there is an alternative decoding package called [Unidecode](https://git - Python `2.7` <-> python-slugify `< 5.0.0` - Python `3.6+` <-> python-slugify `>= 5.0.0` +- Python `3.7+` <-> python-slugify `>= 7.0.0` # How to install diff --git a/slugify/__version__.py b/slugify/__version__.py index 55abc97..184339a 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -5,4 +5,4 @@ __url__ = 'https://github.com/un33k/python-slugify' __license__ = 'MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' -__version__ = '6.1.2' +__version__ = '7.0.0' From dc888f5a7a0c52c0e408a9bded308505cf962fc1 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Tue, 22 Nov 2022 13:05:35 -0500 Subject: [PATCH 093/119] SemVer Table --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2514fc5..04b72ab 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,11 @@ However, there is an alternative decoding package called [Unidecode](https://git ### Python Versions & `Official` Support -- Python `2.7` <-> python-slugify `< 5.0.0` -- Python `3.6+` <-> python-slugify `>= 5.0.0` -- Python `3.7+` <-> python-slugify `>= 7.0.0` +| python version | python-slugify version | +| -------------- | ---------------------- | +| `=2.7` | `< 5.0.0` | +| `<=3.6` | `>= 5.0.0 < 7.0.0` | +| `>=3.7` | `>= 7.0.0` | # How to install From 40a9b99f95293309297c679959de1a506b98626f Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Tue, 22 Nov 2022 13:12:16 -0500 Subject: [PATCH 094/119] Support Matrix --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 04b72ab..26cbc4d 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,13 @@ This module, by default installs and uses [text-unidecode](https://github.com/km However, there is an alternative decoding package called [Unidecode](https://github.com/avian2/unidecode) _(GPL)_. It can be installed as `python-slugify[unidecode]` for those who prefer it. -### Python Versions & `Official` Support +### `Official` Support Matrix -| python version | python-slugify version | -| -------------- | ---------------------- | -| `=2.7` | `< 5.0.0` | -| `<=3.6` | `>= 5.0.0 < 7.0.0` | -| `>=3.7` | `>= 7.0.0` | +| Python | Slugify | +| -------------- | ------------------ | +| `>= 2.7 < 3.6` | `< 5.0.0` | +| `>= 3.6 < 3.7` | `>= 5.0.0 < 7.0.0` | +| `>= 3.7` | `>= 7.0.0` | # How to install From 22d7e84b95869dd4300952b88113dd00e53e9ec7 Mon Sep 17 00:00:00 2001 From: Maksym Shalenyi Date: Fri, 27 Jan 2023 14:47:04 -0800 Subject: [PATCH 095/119] Update import order for unidecode vs text_unidecode (#126) AS is `text_unidecode` is install_requires it is always present in the python environment, it makes sense to try import optinal dependency `unidecode` first, and only then fallback to `text_unidecode`. --- slugify/slugify.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slugify/slugify.py b/slugify/slugify.py index b8c02ad..5354fa5 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -4,9 +4,9 @@ from html.entities import name2codepoint try: - import text_unidecode as unidecode -except ImportError: import unidecode +except ImportError: + import text_unidecode as unidecode __all__ = ['slugify', 'smart_truncate'] From 11e6209bba742675bda4307a65d69c8821fccaee Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Fri, 27 Jan 2023 17:53:09 -0500 Subject: [PATCH 096/119] By default, prefer unidecode if installed --- CHANGELOG.md | 4 ++++ README.md | 2 +- slugify/__version__.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e32d42..e5e9ffb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 8.0.0 + +- By default, prefer unidecode if installed (@enkidulan - thx) + ## 7.0.0 - Drop python 3.6, add python 3.11 (@hugovk - thx) diff --git a/README.md b/README.md index 26cbc4d..aac2082 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This module, by default installs and uses [text-unidecode](https://github.com/kmike/text-unidecode) _(GPL & Perl Artistic)_ for its decoding needs. -However, there is an alternative decoding package called [Unidecode](https://github.com/avian2/unidecode) _(GPL)_. It can be installed as `python-slugify[unidecode]` for those who prefer it. +However, there is an alternative decoding package called [Unidecode](https://github.com/avian2/unidecode) _(GPL)_. It can be installed as `python-slugify[unidecode]` for those who prefer it. `Unidecode` is believed to be more advanced. ### `Official` Support Matrix diff --git a/slugify/__version__.py b/slugify/__version__.py index 184339a..71caf04 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -5,4 +5,4 @@ __url__ = 'https://github.com/un33k/python-slugify' __license__ = 'MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' -__version__ = '7.0.0' +__version__ = '8.0.0' From b9b8b96430dcdd06d7cc888b04485be075a960a4 Mon Sep 17 00:00:00 2001 From: Joris Date: Fri, 24 Feb 2023 16:51:01 +0100 Subject: [PATCH 097/119] Add note on licensing (#130) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index aac2082..64b9204 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,10 @@ Please read the ([wiki](https://github.com/un33k/python-slugify/wiki/Python-Slug Released under a ([MIT](LICENSE)) license. +### Notes on GPL dependencies +Though the dependencies may be GPL licensed, `python-slugify` itself is not considered a derivative work and will remain under the MIT license. +If you wish to avoid installation of any GPL licensed packages, please note that the default dependency `text-unidecode` explicitly lets you choose to use the [Artistic License](https://opensource.org/license/artistic-perl-1-0-2/) instead. Use without concern. + # Version X.Y.Z Version From e20c5e521416d53b37d823540f03145c298daf11 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Fri, 24 Feb 2023 10:58:09 -0500 Subject: [PATCH 098/119] Legal Notice / Readme --- CHANGELOG.md | 4 ++++ slugify/__version__.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5e9ffb..8012b1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 8.0.1 + +- Added license notice to readme (@C-nit - thx) + ## 8.0.0 - By default, prefer unidecode if installed (@enkidulan - thx) diff --git a/slugify/__version__.py b/slugify/__version__.py index 71caf04..a558d9b 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -5,4 +5,4 @@ __url__ = 'https://github.com/un33k/python-slugify' __license__ = 'MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' -__version__ = '8.0.0' +__version__ = '8.0.1' From cab324665b19ab661421697da79ebc902e0ea57c Mon Sep 17 00:00:00 2001 From: AvidCoderr Date: Fri, 24 Feb 2023 11:19:12 -0500 Subject: [PATCH 099/119] unidecode (advanced) hyperlink --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 64b9204..b74b8fe 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This module, by default installs and uses [text-unidecode](https://github.com/kmike/text-unidecode) _(GPL & Perl Artistic)_ for its decoding needs. -However, there is an alternative decoding package called [Unidecode](https://github.com/avian2/unidecode) _(GPL)_. It can be installed as `python-slugify[unidecode]` for those who prefer it. `Unidecode` is believed to be more advanced. +However, there is an alternative decoding package called [Unidecode](https://github.com/avian2/unidecode) _(GPL)_. It can be installed as `python-slugify[unidecode]` for those who prefer it. `Unidecode` is believed to be more [advanced](https://github.com/un33k/python-slugify/wiki/Python-Slugify-Wiki#notes-on-unidecode). ### `Official` Support Matrix From 59eb957b26b3a5049a12aad951cd7e140909d0e1 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 9 Oct 2023 15:56:55 +0200 Subject: [PATCH 100/119] Replace flake8 and pycodestyle with ruff (#131) * Replace flake8 and pycodestyle with ruff * flake8 --> ruff * dev.requirements.txt: ruff==0.0.285, twine==4.0.2 * pip install -t dev.requirements.txt * Ruff --- .github/workflows/ci.yml | 22 ++++++++++------------ .github/workflows/dev.yml | 22 ++++++++++------------ .github/workflows/main.yml | 22 ++++++++++------------ dev.requirements.txt | 5 ++--- 4 files changed, 32 insertions(+), 39 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56e4097..0fea74c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] + python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.9] steps: - uses: actions/checkout@v3 @@ -22,22 +22,20 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} + cache: 'pip' + check-latest: true - name: Install dependencies run: | python -m pip install --upgrade pip pip install -e . - pip install coveralls --upgrade - - name: Run flake8 - run: | - pip install flake8 --upgrade - flake8 --exclude=build --ignore=E501,F403,F401,E241,E225,E128 . - - name: Run pycodestyle - run: | - pip install pycodestyle --upgrade - pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py + pip install -r dev.requirements.txt + pip install --upgrade coveralls + - name: Run ruff + run: ruff --exclude=build --format=github + --select="A,B,DJ,E,F,PLC,PLE,PLW,W" + --ignore=F401,F403 --line-length=117 . - name: Run test - run: | - coverage run --source=slugify test.py + run: coverage run --source=slugify test.py - name: Coveralls run: coveralls --service=github env: diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 73f2f07..552eac6 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] + python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.9] steps: - uses: actions/checkout@v3 @@ -22,22 +22,20 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} + cache: 'pip' + check-latest: true - name: Install dependencies run: | python -m pip install --upgrade pip pip install -e . - pip install coveralls --upgrade - - name: Run flake8 - run: | - pip install flake8 --upgrade - flake8 --exclude=build --ignore=E501,F403,F401,E241,E225,E128 . - - name: Run pycodestyle - run: | - pip install pycodestyle --upgrade - pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py + pip install -r dev.requirements.txt + pip install --upgrade coveralls + - name: Run ruff + run: ruff --exclude=build --format=github + --select="A,B,DJ,E,F,PLC,PLE,PLW,W" + --ignore=F401,F403 --line-length=117 . - name: Run test - run: | - coverage run --source=slugify test.py + run: coverage run --source=slugify test.py - name: Coveralls run: coveralls --service=github env: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a4ae9ad..e76ef64 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] + python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.9] steps: - uses: actions/checkout@v3 @@ -21,22 +21,20 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} + cache: 'pip' + check-latest: true - name: Install dependencies run: | python -m pip install --upgrade pip pip install -e . - pip install coveralls --upgrade - - name: Run flake8 - run: | - pip install flake8 --upgrade - flake8 --exclude=build --ignore=E501,F403,F401,E241,E225,E128 . - - name: Run pycodestyle - run: | - pip install pycodestyle --upgrade - pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py + pip install -r dev.requirements.txt + pip install --upgrade coveralls + - name: Run ruff + run: ruff --exclude=build --format=github + --select="A,B,DJ,E,F,PLC,PLE,PLW,W" + --ignore=F401,F403 --line-length=117 . - name: Run test - run: | - coverage run --source=slugify test.py + run: coverage run --source=slugify test.py - name: Coveralls run: coveralls --service=github env: diff --git a/dev.requirements.txt b/dev.requirements.txt index 5f94d7b..1f1844b 100644 --- a/dev.requirements.txt +++ b/dev.requirements.txt @@ -1,3 +1,2 @@ -pycodestyle==2.8.0 -twine==3.4.1 -flake8==4.0.1 \ No newline at end of file +ruff==0.0.285 +twine==4.0.2 From ef375be69f5758d3b0fb0f041ee5a11cff73ceb2 Mon Sep 17 00:00:00 2001 From: AvidCoderr Date: Mon, 9 Oct 2023 10:15:04 -0400 Subject: [PATCH 101/119] Revert "Replace flake8 and pycodestyle with ruff (#131)" (#136) This reverts commit 59eb957b26b3a5049a12aad951cd7e140909d0e1. --- .github/workflows/ci.yml | 22 ++++++++++++---------- .github/workflows/dev.yml | 22 ++++++++++++---------- .github/workflows/main.yml | 22 ++++++++++++---------- dev.requirements.txt | 5 +++-- 4 files changed, 39 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0fea74c..56e4097 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.9] + python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] steps: - uses: actions/checkout@v3 @@ -22,20 +22,22 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - cache: 'pip' - check-latest: true - name: Install dependencies run: | python -m pip install --upgrade pip pip install -e . - pip install -r dev.requirements.txt - pip install --upgrade coveralls - - name: Run ruff - run: ruff --exclude=build --format=github - --select="A,B,DJ,E,F,PLC,PLE,PLW,W" - --ignore=F401,F403 --line-length=117 . + pip install coveralls --upgrade + - name: Run flake8 + run: | + pip install flake8 --upgrade + flake8 --exclude=build --ignore=E501,F403,F401,E241,E225,E128 . + - name: Run pycodestyle + run: | + pip install pycodestyle --upgrade + pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py - name: Run test - run: coverage run --source=slugify test.py + run: | + coverage run --source=slugify test.py - name: Coveralls run: coveralls --service=github env: diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 552eac6..73f2f07 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.9] + python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] steps: - uses: actions/checkout@v3 @@ -22,20 +22,22 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - cache: 'pip' - check-latest: true - name: Install dependencies run: | python -m pip install --upgrade pip pip install -e . - pip install -r dev.requirements.txt - pip install --upgrade coveralls - - name: Run ruff - run: ruff --exclude=build --format=github - --select="A,B,DJ,E,F,PLC,PLE,PLW,W" - --ignore=F401,F403 --line-length=117 . + pip install coveralls --upgrade + - name: Run flake8 + run: | + pip install flake8 --upgrade + flake8 --exclude=build --ignore=E501,F403,F401,E241,E225,E128 . + - name: Run pycodestyle + run: | + pip install pycodestyle --upgrade + pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py - name: Run test - run: coverage run --source=slugify test.py + run: | + coverage run --source=slugify test.py - name: Coveralls run: coveralls --service=github env: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e76ef64..a4ae9ad 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.9] + python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] steps: - uses: actions/checkout@v3 @@ -21,20 +21,22 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - cache: 'pip' - check-latest: true - name: Install dependencies run: | python -m pip install --upgrade pip pip install -e . - pip install -r dev.requirements.txt - pip install --upgrade coveralls - - name: Run ruff - run: ruff --exclude=build --format=github - --select="A,B,DJ,E,F,PLC,PLE,PLW,W" - --ignore=F401,F403 --line-length=117 . + pip install coveralls --upgrade + - name: Run flake8 + run: | + pip install flake8 --upgrade + flake8 --exclude=build --ignore=E501,F403,F401,E241,E225,E128 . + - name: Run pycodestyle + run: | + pip install pycodestyle --upgrade + pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py - name: Run test - run: coverage run --source=slugify test.py + run: | + coverage run --source=slugify test.py - name: Coveralls run: coveralls --service=github env: diff --git a/dev.requirements.txt b/dev.requirements.txt index 1f1844b..5f94d7b 100644 --- a/dev.requirements.txt +++ b/dev.requirements.txt @@ -1,2 +1,3 @@ -ruff==0.0.285 -twine==4.0.2 +pycodestyle==2.8.0 +twine==3.4.1 +flake8==4.0.1 \ No newline at end of file From a07254479b83e7a12fad9db1f481e1ab5fd5ce0d Mon Sep 17 00:00:00 2001 From: AvidCoderr Date: Mon, 9 Oct 2023 10:20:54 -0400 Subject: [PATCH 102/119] Add typing and expose py.typed (#137) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add typing and expose ``py.typed`` (#135) * actions order * Revert "actions order" This reverts commit 716c2c7ae3bbb13ff9c972cef769ca957319a1e0. * Revert "Merge branch 'master' into staging" This reverts commit e1699be5b697e6cb016e42c342f5fc2d4742e90c, reversing changes made to c3fe5741141142f9bcc3750d095b534afba02fed. --------- Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- CHANGELOG.md | 4 ++++ README.md | 28 ++++++++++++++-------------- setup.py | 5 ++++- slugify/__main__.py | 10 ++++++---- slugify/py.typed | 0 slugify/slugify.py | 29 +++++++++++++++++++++++++---- slugify/special.py | 5 +++-- 7 files changed, 56 insertions(+), 25 deletions(-) create mode 100644 slugify/py.typed diff --git a/CHANGELOG.md b/CHANGELOG.md index 8012b1c..b91e76e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## Work in progress + +- Added typing to API and expose `py.typed`. + ## 8.0.1 - Added license notice to readme (@C-nit - thx) diff --git a/README.md b/README.md index b74b8fe..0dfbd92 100644 --- a/README.md +++ b/README.md @@ -34,20 +34,20 @@ However, there is an alternative decoding package called [Unidecode](https://git ```python def slugify( - text, - entities=True, - decimal=True, - hexadecimal=True, - max_length=0, - word_boundary=False, - separator='-', - save_order=False, - stopwords=(), - regex_pattern=None, - lowercase=True, - replacements=(), - allow_unicode=False - ): + text: str, + entities: bool = True, + decimal: bool = True, + hexadecimal: bool = True, + max_length: int = 0, + word_boundary: bool = False, + separator: str = DEFAULT_SEPARATOR, + save_order: bool = False, + stopwords: Iterable[str] = (), + regex_pattern: str | None = None, + lowercase: bool = True, + replacements: Iterable[Iterable[str]] = (), + allow_unicode: bool = False, +) -> str: """ Make a slug from the given text. :param text (str): initial text diff --git a/setup.py b/setup.py index 65c72f4..b0e7b25 100755 --- a/setup.py +++ b/setup.py @@ -58,7 +58,10 @@ def status(s): url=about['__url__'], license=about['__license__'], packages=[package], - package_data={'': ['LICENSE']}, + package_data={ + '': ['LICENSE'], + 'slugify': ['py.typed'], + }, package_dir={'slugify': 'slugify'}, include_package_data=True, python_requires=python_requires, diff --git a/slugify/__main__.py b/slugify/__main__.py index 7dd6b01..d31a6bb 100644 --- a/slugify/__main__.py +++ b/slugify/__main__.py @@ -1,11 +1,13 @@ -from __future__ import print_function, absolute_import +from __future__ import absolute_import, annotations, print_function + import argparse import sys +from typing import Any from .slugify import slugify, DEFAULT_SEPARATOR -def parse_args(argv): +def parse_args(argv: list[str]) -> argparse.Namespace: parser = argparse.ArgumentParser(description="Slug string") input_group = parser.add_argument_group(description="Input") @@ -63,7 +65,7 @@ def split_check(repl): return args -def slugify_params(args): +def slugify_params(args: argparse.Namespace) -> dict[str, Any]: return dict( text=args.input_string, entities=args.entities, @@ -80,7 +82,7 @@ def slugify_params(args): ) -def main(argv=None): # pragma: no cover +def main(argv: list[str] | None = None): # pragma: no cover """ Run this program """ if argv is None: argv = sys.argv diff --git a/slugify/py.typed b/slugify/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/slugify/slugify.py b/slugify/slugify.py index 5354fa5..21bdaeb 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -1,6 +1,9 @@ +from __future__ import annotations + import re import sys import unicodedata +from collections.abc import Iterable from html.entities import name2codepoint try: @@ -22,7 +25,13 @@ DEFAULT_SEPARATOR = '-' -def smart_truncate(string, max_length=0, word_boundary=False, separator=' ', save_order=False): +def smart_truncate( + string: str, + max_length: int = 0, + word_boundary: bool = False, + separator: str = " ", + save_order: bool = False, +) -> str: """ Truncate a string. :param string (str): string for modification @@ -64,9 +73,21 @@ def smart_truncate(string, max_length=0, word_boundary=False, separator=' ', sav return truncated.strip(separator) -def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, word_boundary=False, - separator=DEFAULT_SEPARATOR, save_order=False, stopwords=(), regex_pattern=None, lowercase=True, - replacements=(), allow_unicode=False): +def slugify( + text: str, + entities: bool = True, + decimal: bool = True, + hexadecimal: bool = True, + max_length: int = 0, + word_boundary: bool = False, + separator: str = DEFAULT_SEPARATOR, + save_order: bool = False, + stopwords: Iterable[str] = (), + regex_pattern: str | None = None, + lowercase: bool = True, + replacements: Iterable[Iterable[str]] = (), + allow_unicode: bool = False, +) -> str: """ Make a slug from the given text. :param text (str): initial text diff --git a/slugify/special.py b/slugify/special.py index 54eb85c..0b602cf 100644 --- a/slugify/special.py +++ b/slugify/special.py @@ -1,7 +1,7 @@ -# -*- coding: utf-8 -*- +from __future__ import annotations -def add_uppercase_char(char_list): +def add_uppercase_char(char_list: list[tuple[str, str]]) -> list[tuple[str, str]]: """ Given a replacement char list, this adds uppercase chars to the list """ for item in char_list: @@ -10,6 +10,7 @@ def add_uppercase_char(char_list): if upper_dict not in char_list and char != upper_dict[0]: char_list.insert(0, upper_dict) return char_list + return char_list # Language specific pre translations From 499112de56e19f6ef0dcee32adff3696e4ef5753 Mon Sep 17 00:00:00 2001 From: Jelmer Date: Wed, 25 Oct 2023 18:27:18 +0200 Subject: [PATCH 103/119] Add Python 3.12 to test matrix and add classifier to the setup (#139) --- .github/workflows/ci.yml | 2 +- .github/workflows/dev.yml | 2 +- .github/workflows/main.yml | 2 +- CHANGELOG.md | 1 + setup.py | 1 + 5 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56e4097..36959b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] + python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.8] steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 73f2f07..c12b80a 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] + python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.8] steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a4ae9ad..eb66dc5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] + python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.8] steps: - uses: actions/checkout@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index b91e76e..395e538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Work in progress - Added typing to API and expose `py.typed`. +- Formally support 3.12 ## 8.0.1 diff --git a/setup.py b/setup.py index b0e7b25..9661638 100755 --- a/setup.py +++ b/setup.py @@ -83,6 +83,7 @@ def status(s): 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', ], entry_points={'console_scripts': ['slugify=slugify.__main__:main']}, ) From e52c35e34899bc21a389aa7f4fe5084423cf538c Mon Sep 17 00:00:00 2001 From: AvidCoderr Date: Thu, 25 Jan 2024 12:04:54 -0500 Subject: [PATCH 104/119] Ci - Normalize accented text twice. (#143) * pre normalize, upversion node support in ci/cd, more test --- .github/workflows/ci.yml | 4 ++-- .github/workflows/dev.yml | 4 ++-- .github/workflows/main.yml | 4 ++-- CHANGELOG.md | 5 +++-- slugify/__version__.py | 2 +- slugify/slugify.py | 9 ++++++--- test.py | 4 ++++ 7 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36959b0..71bc219 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,9 +17,9 @@ jobs: python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.8] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Install dependencies diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index c12b80a..88791a7 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -17,9 +17,9 @@ jobs: python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.8] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Install dependencies diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index eb66dc5..7a9c77e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,9 +16,9 @@ jobs: python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.8] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Install dependencies diff --git a/CHANGELOG.md b/CHANGELOG.md index 395e538..eb60bee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ ## Work in progress -- Added typing to API and expose `py.typed`. -- Formally support 3.12 +## 8.0.2 + +- Normalize text before converting to unicode. (@chuckyblack - thx) ## 8.0.1 diff --git a/slugify/__version__.py b/slugify/__version__.py index a558d9b..dbbff9f 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -5,4 +5,4 @@ __url__ = 'https://github.com/un33k/python-slugify' __license__ = 'MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' -__version__ = '8.0.1' +__version__ = '8.0.2' diff --git a/slugify/slugify.py b/slugify/slugify.py index 21bdaeb..9242e3e 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -118,8 +118,11 @@ def slugify( # replace quotes with dashes - pre-process text = QUOTE_PATTERN.sub(DEFAULT_SEPARATOR, text) - # decode unicode - if not allow_unicode: + # normalize text, convert to unicode if required + if allow_unicode: + text = unicodedata.normalize('NFKC', text) + else: + text = unicodedata.normalize('NFKD', text) text = unidecode.unidecode(text) # ensure text is still in unicode @@ -144,7 +147,7 @@ def slugify( except Exception: pass - # translate + # re normalize text if allow_unicode: text = unicodedata.normalize('NFKC', text) else: diff --git a/test.py b/test.py index 931f38f..2534499 100644 --- a/test.py +++ b/test.py @@ -36,6 +36,10 @@ def test_phonetic_conversion_of_eastern_scripts(self): self.assertEqual(r, "ying-shi-ma") def test_accented_text(self): + txt = '𝐚́́𝕒́àáâäãąā' + r = slugify(txt) + self.assertEqual(r, "aaaaaaaaa") + txt = 'C\'est déjà l\'été.' r = slugify(txt) self.assertEqual(r, "c-est-deja-l-ete") From 26b81c2e224ebb65c7fba40d37d17d762be3782f Mon Sep 17 00:00:00 2001 From: AvidCoderr Date: Wed, 31 Jan 2024 13:16:35 -0500 Subject: [PATCH 105/119] Drop compatibility for unsupported Python Version (#147) Drop compatibility for unsupported Python Version (8.0.3) ---- Co-authored-by: Viicos <65306057+Viicos@users.noreply.github.com> --- CHANGELOG.md | 5 ++++- slugify/__main__.py | 2 +- slugify/__version__.py | 2 +- slugify/slugify.py | 6 +----- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb60bee..d64cb0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ -## Work in progress +## 8.0.3 + +- Drop compatibility for unsupported Python Version (@Viicos - thx) +- Fix pattern types. ## 8.0.2 diff --git a/slugify/__main__.py b/slugify/__main__.py index d31a6bb..4cc4616 100644 --- a/slugify/__main__.py +++ b/slugify/__main__.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import, annotations, print_function +from __future__ import annotations import argparse import sys diff --git a/slugify/__version__.py b/slugify/__version__.py index dbbff9f..12c76b6 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -5,4 +5,4 @@ __url__ = 'https://github.com/un33k/python-slugify' __license__ = 'MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' -__version__ = '8.0.2' +__version__ = '8.0.3' diff --git a/slugify/slugify.py b/slugify/slugify.py index 9242e3e..09c7e07 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -1,7 +1,6 @@ from __future__ import annotations import re -import sys import unicodedata from collections.abc import Iterable from html.entities import name2codepoint @@ -83,7 +82,7 @@ def slugify( separator: str = DEFAULT_SEPARATOR, save_order: bool = False, stopwords: Iterable[str] = (), - regex_pattern: str | None = None, + regex_pattern: re.Pattern[str] | str | None = None, lowercase: bool = True, replacements: Iterable[Iterable[str]] = (), allow_unicode: bool = False, @@ -153,9 +152,6 @@ def slugify( else: text = unicodedata.normalize('NFKD', text) - if sys.version_info < (3,): - text = text.encode('ascii', 'ignore') - # make the text lowercase (optional) if lowercase: text = text.lower() From 243354893ee8cc270aaae82eff5fe3ae37481439 Mon Sep 17 00:00:00 2001 From: Val N Date: Thu, 8 Feb 2024 13:31:20 -0500 Subject: [PATCH 106/119] Uppercase handling of special chars (#149) * Remove compatibility with unsupported Python versions (#146) * fix uppercase pre-translations (#148) Co-authored-by: Viicos <65306057+Viicos@users.noreply.github.com> Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com> --- CHANGELOG.md | 4 ++++ slugify/__version__.py | 2 +- slugify/special.py | 1 - test.py | 5 ++++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d64cb0e..9aa0ef2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 8.0.4 + +- Properly handle uppercase special characters (@mib1185 - thx) + ## 8.0.3 - Drop compatibility for unsupported Python Version (@Viicos - thx) diff --git a/slugify/__version__.py b/slugify/__version__.py index 12c76b6..854038e 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -5,4 +5,4 @@ __url__ = 'https://github.com/un33k/python-slugify' __license__ = 'MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' -__version__ = '8.0.3' +__version__ = '8.0.4' diff --git a/slugify/special.py b/slugify/special.py index 0b602cf..918cb2a 100644 --- a/slugify/special.py +++ b/slugify/special.py @@ -9,7 +9,6 @@ def add_uppercase_char(char_list: list[tuple[str, str]]) -> list[tuple[str, str] upper_dict = char.upper(), xlate.capitalize() if upper_dict not in char_list and char != upper_dict[0]: char_list.insert(0, upper_dict) - return char_list return char_list diff --git a/test.py b/test.py index 2534499..d13ef94 100644 --- a/test.py +++ b/test.py @@ -4,6 +4,7 @@ import unittest from contextlib import contextmanager +from slugify import PRE_TRANSLATIONS from slugify import slugify from slugify import smart_truncate from slugify.__main__ import slugify_params, parse_args @@ -236,9 +237,11 @@ def test_replacements_german_umlaut_custom(self): r = slugify(txt, replacements=[['Ü', 'UE'], ['ü', 'ue']]) self.assertEqual(r, "ueber-ueber-german-umlaut") + def test_pre_translation(self): + self.assertEqual(PRE_TRANSLATIONS, [('Ю', 'U'), ('Щ', 'Sch'), ('У', 'Y'), ('Х', 'H'), ('Я', 'Ya'), ('Ё', 'E'), ('ё', 'e'), ('я', 'ya'), ('х', 'h'), ('у', 'y'), ('щ', 'sch'), ('ю', 'u'), ('Ü', 'Ue'), ('Ö', 'Oe'), ('Ä', 'Ae'), ('ä', 'ae'), ('ö', 'oe'), ('ü', 'ue'), ('Ϋ́', 'Y'), ('Ϋ', 'Y'), ('Ύ', 'Y'), ('Υ', 'Y'), ('Χ', 'Ch'), ('χ', 'ch'), ('Ξ', 'X'), ('ϒ', 'Y'), ('υ', 'y'), ('ύ', 'y'), ('ϋ', 'y'), ('ΰ', 'y')]) -class TestSlugifyUnicode(unittest.TestCase): +class TestSlugifyUnicode(unittest.TestCase): def test_extraneous_seperators(self): txt = "This is a test ---" From efa13c6596b4c55a6cd1b0fe5d3ecf0de793e431 Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Fri, 1 Mar 2024 13:19:24 -0500 Subject: [PATCH 107/119] add tea file --- tea.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 tea.yml diff --git a/tea.yml b/tea.yml new file mode 100644 index 0000000..dda3df9 --- /dev/null +++ b/tea.yml @@ -0,0 +1,7 @@ +# https://tea.xyz/what-is-this-file +--- +version: 1.0.0 +codeOwners: + - '0xaC8Bb28685BD43FD784DC902E132829c6C6DafA2' +quorum: 1 + From 872b37509399a7f02e53f46ad9881f63f66d334b Mon Sep 17 00:00:00 2001 From: "Val Neekman (AvidCoder)" Date: Fri, 1 Mar 2024 13:21:15 -0500 Subject: [PATCH 108/119] rename tea --- tea.yml => tea.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tea.yml => tea.yaml (100%) diff --git a/tea.yml b/tea.yaml similarity index 100% rename from tea.yml rename to tea.yaml From 6f381ac152ec1bf2ba7686cbf7d0c23b3d5d370b Mon Sep 17 00:00:00 2001 From: Mark Steward Date: Fri, 26 Sep 2025 16:19:57 +0100 Subject: [PATCH 109/119] Explain `save_order` more clearly (#157) --- README.md | 12 ++++++++---- slugify/slugify.py | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0dfbd92..c796aa4 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ def slugify( :param hexadecimal (bool): converts html hexadecimal to unicode (Ž -> Ž -> z) :param max_length (int): output string length :param word_boundary (bool): truncates to end of full words (length may be shorter than max_length) - :param save_order (bool): if parameter is True and max_length > 0 return whole words in the initial order + :param save_order (bool): when set, does not include shorter subsequent words even if they fit :param separator (str): separator between words :param stopwords (iterable): words to discount :param regex_pattern (str): regex pattern for disallowed characters @@ -108,9 +108,13 @@ txt = 'jaja---lol-méméméoo--a' r = slugify(txt, max_length=20, word_boundary=True, separator=".") self.assertEqual(r, "jaja.lol.mememeoo.a") -txt = 'one two three four five' -r = slugify(txt, max_length=13, word_boundary=True, save_order=True) -self.assertEqual(r, "one-two-three") +txt = 'one two three four' +r = slugify(txt, max_length=12, word_boundary=True, save_order=False) +self.assertEqual(r, "one-two-four") + +txt = 'one two three four' +r = slugify(txt, max_length=12, word_boundary=True, save_order=True) +self.assertEqual(r, "one-two") txt = 'the quick brown fox jumps over the lazy dog' r = slugify(txt, stopwords=['the']) diff --git a/slugify/slugify.py b/slugify/slugify.py index 09c7e07..67b31c8 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -95,7 +95,7 @@ def slugify( :param hexadecimal (bool): converts html hexadecimal to unicode :param max_length (int): output string length :param word_boundary (bool): truncates to complete word even if length ends up shorter than max_length - :param save_order (bool): if parameter is True and max_length > 0 return whole words in the initial order + :param save_order (bool): when set, does not include shorter subsequent words even if they fit :param separator (str): separator between words :param stopwords (iterable): words to discount :param regex_pattern (str): regex pattern for disallowed characters From d5643b16caf3f312d39574dccf49763da0908eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Fri, 26 Sep 2025 09:20:36 -0600 Subject: [PATCH 110/119] Add Python 3.13 to test matrix and add classifier to the setup (#154) --- .github/workflows/ci.yml | 2 +- .github/workflows/dev.yml | 2 +- .github/workflows/main.yml | 2 +- setup.py | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71bc219..cb6f443 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.8] + python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, 3.13, pypy3.10] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 88791a7..6046bd2 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.8] + python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, 3.13, pypy3.10] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7a9c77e..d1a8f9c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.8] + python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, 3.13, pypy3.10] steps: - uses: actions/checkout@v4 diff --git a/setup.py b/setup.py index 9661638..293ea26 100755 --- a/setup.py +++ b/setup.py @@ -84,6 +84,7 @@ def status(s): 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', ], entry_points={'console_scripts': ['slugify=slugify.__main__:main']}, ) From 32a390f5b2145b4fab2532d00f59b26f0acac132 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Wed, 15 Oct 2025 17:41:24 -0400 Subject: [PATCH 111/119] Remove references to `easy_install`, which is deprecated --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c796aa4..b0100f2 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,11 @@ However, there is an alternative decoding package called [Unidecode](https://git # How to install - easy_install python-slugify |OR| easy_install python-slugify[unidecode] - -- OR -- - pip install python-slugify |OR| pip install python-slugify[unidecode] + pip install python-slugify + + # OR + + pip install python-slugify[unidecode] # Options From b7b9fa02bb01962d47054bf6525514db1bf8a165 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Tue, 6 Jan 2026 08:40:22 -0600 Subject: [PATCH 112/119] Support py3.14; drop py3.9 and lower --- .github/workflows/ci.yml | 8 +++++++- .github/workflows/dev.yml | 8 +++++++- .github/workflows/main.yml | 8 +++++++- CHANGELOG.md | 5 +++++ setup.py | 6 ++---- 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb6f443..263e360 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, 3.13, pypy3.10] + python: + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "3.14" + - "pypy3.11" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 6046bd2..55119d6 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -14,7 +14,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, 3.13, pypy3.10] + python: + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "3.14" + - "pypy3.11" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d1a8f9c..a794f5e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, 3.13, pypy3.10] + python: + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "3.14" + - "pypy3.11" steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aa0ef2..f062ed0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## Unreleased + +- Support Python 3.14. +- Drop support for Python 3.9 and lower. + ## 8.0.4 - Properly handle uppercase special characters (@mib1185 - thx) diff --git a/setup.py b/setup.py index 293ea26..1d0e2d5 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ package = 'slugify' -python_requires = ">=3.7" +python_requires = ">=3.10" here = os.path.abspath(os.path.dirname(__file__)) install_requires = ['text-unidecode>=1.3'] @@ -78,13 +78,11 @@ def status(s): 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', ], entry_points={'console_scripts': ['slugify=slugify.__main__:main']}, ) From 1ef698fa7a265ec8971d0b641fb6e735dcd667dc Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Tue, 6 Jan 2026 14:54:54 -0600 Subject: [PATCH 113/119] Reintroduce tox and thoroughly test the project This commit introduces the following major changes: * Use tox. * Test the project against both `unidecode` and `text_unidecode`. Previously, `unidecode` was untested. * Drop support for Python 3.7, 3.8, and 3.9. CI was unable to run because Python 3.7 is no longer available. In addition, Python 3.9 and lower are all end-of-life. * Test against PyPy 3.11 and Python 3.14. * Test type annotations using mypy. Previously, type annotations were untested. * Install and test all supported Python interpreters in CI at once. * Use tox, and the tox-uv plugin, exclusively in CI. This eliminates differences between local and CI testing. * Run CI against pull requests. * Collect, combine, and report coverage for all Python versions and dependencies, locally and in CI. In addition, this commit introduces the following minor changes: * Fix typing issues identified by mypy. Note that `text_unidecode` is untyped and must be type-ignored. * Remove almost all coverage `pragma: nocover` lines. * Fix incorrect copy/pasted comments in `ci.yml` and `main.yml`. * Upgrade `actions/checkout` to v5 and `actions/setup-python` to v6. --- .github/workflows/ci.yml | 2 +- .github/workflows/main.yml | 41 +++++++--------------- CHANGELOG.md | 4 +++ pyproject.toml | 41 ++++++++++++++++++++++ slugify/__main__.py | 6 ++-- slugify/slugify.py | 4 +-- test.py | 2 +- tox.ini | 69 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 134 insertions(+), 35 deletions(-) create mode 100644 pyproject.toml create mode 100644 tox.ini diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 263e360..37e11a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ name: CI -# Run on push only for dev/sandbox +# Run on push only for ci/staging # Otherwise it may trigger concurrently `push & pull_request` on PRs. on: push: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a794f5e..ba50793 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,48 +1,33 @@ name: Main -# Run on push only for dev/sandbox -# Otherwise it may trigger concurrently `push & pull_request` on PRs. on: + pull_request: null push: branches: - master jobs: build: - name: Python ${{ matrix.python }} + name: Linux runs-on: ubuntu-latest - strategy: - matrix: - python: - - "3.10" - - "3.11" - - "3.12" - - "3.13" - - "3.14" - - "pypy3.11" - steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: setup python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: ${{ matrix.python }} + python-version: | + pypy3.11 + 3.10 + 3.11 + 3.12 + 3.13 + 3.14 - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install -e . - pip install coveralls --upgrade - - name: Run flake8 - run: | - pip install flake8 --upgrade - flake8 --exclude=build --ignore=E501,F403,F401,E241,E225,E128 . - - name: Run pycodestyle - run: | - pip install pycodestyle --upgrade - pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py + python -m pip install coveralls tox tox-uv - name: Run test run: | - coverage run --source=slugify test.py + tox - name: Coveralls run: coveralls --service=github env: diff --git a/CHANGELOG.md b/CHANGELOG.md index f062ed0..400399f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ - Support Python 3.14. - Drop support for Python 3.9 and lower. +- Use tox for local test runs and in CI. +- Test the project against both `unidecode` and `text_unidecode`. +- Fix type annotation issues identified by mypy. +- Run CI against pull requests. ## 8.0.4 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..de62727 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,41 @@ +# coverage +# -------- + +[tool.coverage.run] +relative_files = true +parallel = true +branch = true +source = [ + "slugify", + "test", +] + +[tool.coverage.paths] +source = [ + "src", + "*/site-packages", +] + +[tool.coverage.report] +skip_covered = true +fail_under = 97 + + +# mypy +# ---- + +[tool.mypy] +packages = "slugify" +strict = true +sqlite_cache = true + + +# pytest +# ------ + +[tool.pytest.ini_options] +testpaths = ["test.py"] +addopts = "--color=yes" +filterwarnings = [ + "error", +] diff --git a/slugify/__main__.py b/slugify/__main__.py index 4cc4616..4e6b3d9 100644 --- a/slugify/__main__.py +++ b/slugify/__main__.py @@ -47,7 +47,7 @@ def parse_args(argv: list[str]) -> argparse.Namespace: parser.error("Input strings and --stdin cannot work together") if args.replacements: - def split_check(repl): + def split_check(repl: str) -> list[str]: SEP = '->' if SEP not in repl: parser.error("Replacements must be of the form: ORIGINAL{SEP}REPLACED".format(SEP=SEP)) @@ -82,7 +82,7 @@ def slugify_params(args: argparse.Namespace) -> dict[str, Any]: ) -def main(argv: list[str] | None = None): # pragma: no cover +def main(argv: list[str] | None = None) -> None: """ Run this program """ if argv is None: argv = sys.argv @@ -94,5 +94,5 @@ def main(argv: list[str] | None = None): # pragma: no cover sys.exit(-1) -if __name__ == '__main__': # pragma: no cover +if __name__ == '__main__': main() diff --git a/slugify/slugify.py b/slugify/slugify.py index 67b31c8..9b5f27f 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -8,7 +8,7 @@ try: import unidecode except ImportError: - import text_unidecode as unidecode + import text_unidecode as unidecode # type: ignore[import-untyped, no-redef] __all__ = ['slugify', 'smart_truncate'] @@ -67,7 +67,7 @@ def smart_truncate( else: if save_order: break - if not truncated: # pragma: no cover + if not truncated: truncated = string[:max_length] return truncated.strip(separator) diff --git a/test.py b/test.py index d13ef94..fcec4b6 100644 --- a/test.py +++ b/test.py @@ -653,5 +653,5 @@ def test_multivalued_options_with_text(self): self.assertEqual(params['stopwords'], ['the', 'in', 'a', 'hurry']) -if __name__ == '__main__': +if __name__ == '__main__': # pragma: nocover unittest.main() diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..0c16f5e --- /dev/null +++ b/tox.ini @@ -0,0 +1,69 @@ +[tox] +env_list = + coverage-erase + py{3.10, 3.11, 3.12, 3.13, 3.14}-{unidecode, text_unidecode} + pypy{3.11}-{unidecode, text_unidecode} + coverage-report + coverage-html + mypy + pycodestyle + +[testenv] +depends = + py{3.10, 3.11, 3.12, 3.13, 3.14}-{unidecode, text_unidecode}: coverage-erase + pypy{3.11}-{unidecode, text_unidecode}: coverage-erase +deps = + coverage[toml] + pytest + unidecode: pip + unidecode: unidecode +commands_pre: + # If testing unidecode, ensure text_unidecode is unavailable. + unidecode: pip uninstall --yes text_unidecode +commands = + coverage run -m pytest test.py + +[testenv:coverage_base] +deps = + coverage[toml] + +[testenv:coverage-erase] +base = coverage_base +commands = + coverage erase + +[testenv:coverage-report] +base = coverage_base +depends = + py{3.10, 3.11, 3.12, 3.13, 3.14}-{unidecode, text_unidecode} + pypy{3.11}-{unidecode, text_unidecode} +commands_pre = + - coverage combine +commands = + coverage report + +[testenv:coverage-html] +base = coverage_base +depends = + coverage-report +commands = + coverage html --fail-under=0 + +[testenv:mypy] +deps = + mypy + unidecode +commands = + mypy + +[testenv:pycodestyle] +deps = + pycodestyle +commands = + pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py + +[testenv:flake8] +deps = + flake8 +commands = + flake8 --ignore=E501,F403,F401,E241,E225,E128 slugify/ setup.py test.py From 13843e1a613a11383903d75e306549aba7f496d1 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Tue, 7 Oct 2025 09:27:03 -0500 Subject: [PATCH 114/119] Remove deprecated `codecs.open()` usage `codecs.open()` is deprecated and throws a `DeprecationWarning`: ``` :20: DeprecationWarning: codecs.open() is deprecated. Use open() instead. :23: DeprecationWarning: codecs.open() is deprecated. Use open() instead. ``` --- setup.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 1d0e2d5..083f7a3 100755 --- a/setup.py +++ b/setup.py @@ -3,7 +3,6 @@ import os import sys -from codecs import open from shutil import rmtree from setuptools import setup @@ -17,10 +16,10 @@ test_requires = [] about = {} -with open(os.path.join(here, package, '__version__.py'), 'r', 'utf-8') as f: +with open(os.path.join(here, package, '__version__.py'), 'r', encoding='utf-8') as f: exec(f.read(), about) -with open('README.md', 'r', 'utf-8') as f: +with open('README.md', 'r', encoding='utf-8') as f: readme = f.read() From d2b069438a32eaf9ee6f1fee1d37e1b183612e3d Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Tue, 7 Oct 2025 09:29:49 -0500 Subject: [PATCH 115/119] Remove the unknown `tests_require` option The `tests_require` option throws a `UserWarning`: ``` /.../site-packages/setuptools/_distutils/dist.py:289: UserWarning: Unknown distribution option: 'tests_require' warnings.warn(msg) ``` --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 083f7a3..eff6a78 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,6 @@ install_requires = ['text-unidecode>=1.3'] extras_requires = {'unidecode': ['Unidecode>=1.1.1']} -test_requires = [] about = {} with open(os.path.join(here, package, '__version__.py'), 'r', encoding='utf-8') as f: @@ -65,7 +64,6 @@ def status(s): include_package_data=True, python_requires=python_requires, install_requires=install_requires, - tests_require=test_requires, extras_require=extras_requires, zip_safe=False, cmdclass={}, From 85b9be8111b3ea17a71e109c62be9c37e723e89f Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Tue, 7 Oct 2025 09:37:46 -0500 Subject: [PATCH 116/119] Resolve deprecated `License` trove classifier usage This resolves build warnings thrown by setuptools: ``` /.../site-packages/setuptools/dist.py:759: SetuptoolsDeprecationWarning: License classifiers are deprecated. !! ******************************************************************************** Please consider removing the following classifiers in favor of a SPDX license expression: License :: OSI Approved :: MIT License See https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license for details. ******************************************************************************** !! ``` --- setup.py | 1 - slugify/__version__.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index eff6a78..32f44dd 100755 --- a/setup.py +++ b/setup.py @@ -72,7 +72,6 @@ def status(s): 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Natural Language :: English', - 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.10', diff --git a/slugify/__version__.py b/slugify/__version__.py index 854038e..a9cd778 100644 --- a/slugify/__version__.py +++ b/slugify/__version__.py @@ -3,6 +3,6 @@ __author_email__ = 'info@neekware.com' __description__ = 'A Python slugify application that also handles Unicode' __url__ = 'https://github.com/un33k/python-slugify' -__license__ = 'MIT' +__license__ = 'SPDX-License-Identifier: MIT' __copyright__ = 'Copyright 2022 Val Neekman @ Neekware Inc.' __version__ = '8.0.4' From 406c65b8ae06b7ababcc1c624364b6b30cf67097 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Tue, 7 Oct 2025 09:40:18 -0500 Subject: [PATCH 117/119] Resolve deprecation warnings thrown when installing in editable mode Running `pip install -e.` displays the following deprecation warnings: ``` DEPRECATION: Legacy editable install of python-slugify==8.0.4 from file:///.../pr-python-slugify (setup.py develop) is deprecated. pip 25.3 will enforce this behaviour change. A possible replacement is to add a pyproject.toml or enable --use-pep517, and use setuptools >= 64. If the resulting installation is not behaving as expected, try using --config-settings editable_mode=compat. Please consult the setuptools documentation for more information. Discussion can be found at https://github.com/pypa/pip/issues/11457 ``` --- pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index de62727..1c02bfe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,8 @@ +[build-system] +requires = ["setuptools>=61.2"] +build-backend = "setuptools.build_meta" + + # coverage # -------- From 733c5bbc5efd275b01799747d460447e94ef75d6 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Tue, 6 Jan 2026 16:49:27 -0600 Subject: [PATCH 118/119] Summarize the build warnings fixes in the CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 400399f..537460e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Test the project against both `unidecode` and `text_unidecode`. - Fix type annotation issues identified by mypy. - Run CI against pull requests. +- Fix package build warnings. ## 8.0.4 From 4faa5f996724c93ee1c4c8a06d461b98f9cd5ebf Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Wed, 7 Jan 2026 07:28:44 -0600 Subject: [PATCH 119/119] Fix the README badge to refer to the `main.yml` workflow results --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b0100f2..e5123f1 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,7 @@ X.Y.Z Version `MINOR` version -- when you add functionality in a backwards-compatible manner, and `PATCH` version -- when you make backwards-compatible bug fixes. -[status-image]: https://github.com/un33k/python-slugify/actions/workflows/ci.yml/badge.svg +[status-image]: https://github.com/un33k/python-slugify/actions/workflows/main.yml/badge.svg [status-link]: https://github.com/un33k/python-slugify/actions/workflows/ci.yml [version-image]: https://img.shields.io/pypi/v/python-slugify.svg [version-link]: https://pypi.python.org/pypi/python-slugify