Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
devel:languages:python
python-python-slugify
python-slugify-8.0.1.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File python-slugify-8.0.1.obscpio of Package python-python-slugify
07070100000000000041ED00000000000000000000000263F8DECF00000000000000000000000000000000000000000000001D00000000python-slugify-8.0.1/.github07070100000001000041ED00000000000000000000000263F8DECF00000000000000000000000000000000000000000000002700000000python-slugify-8.0.1/.github/workflows07070100000002000081A400000000000000000000000163F8DECF000004B7000000000000000000000000000000000000002E00000000python-slugify-8.0.1/.github/workflows/ci.ymlname: 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.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] steps: - uses: actions/checkout@v3 - name: setup python uses: actions/setup-python@v4 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 }} 07070100000003000081A400000000000000000000000163F8DECF000004B9000000000000000000000000000000000000002F00000000python-slugify-8.0.1/.github/workflows/dev.ymlname: 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.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] steps: - uses: actions/checkout@v3 - name: setup python uses: actions/setup-python@v4 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 }} 07070100000004000081A400000000000000000000000163F8DECF000004AD000000000000000000000000000000000000003000000000python-slugify-8.0.1/.github/workflows/main.ymlname: 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.7, 3.8, 3.9, "3.10", 3.11, pypy3.8] steps: - uses: actions/checkout@v3 - name: setup python uses: actions/setup-python@v4 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 }} 07070100000005000081A400000000000000000000000163F8DECF000002E3000000000000000000000000000000000000002000000000python-slugify-8.0.1/.gitignore# JebBrains IDE .idea/ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ *.*DS_Store 07070100000006000041ED00000000000000000000000263F8DECF00000000000000000000000000000000000000000000001D00000000python-slugify-8.0.1/.vscode07070100000007000081A400000000000000000000000163F8DECF00000087000000000000000000000000000000000000002B00000000python-slugify-8.0.1/.vscode/settings.json{ "python.linting.pylintEnabled": false, "python.pythonPath": "/usr/bin/python3", "cSpell.words": ["Neekman", "shch", "xlate"] } 07070100000008000081A400000000000000000000000163F8DECF00000E9C000000000000000000000000000000000000002200000000python-slugify-8.0.1/CHANGELOG.md## 8.0.1 - Added license notice to readme (@C-nit - thx) ## 8.0.0 - By default, prefer unidecode if installed (@enkidulan - thx) ## 7.0.0 - Drop python 3.6, add python 3.11 (@hugovk - thx) ## 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 ## 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) - Drop support for python 2.7, 3.5 & friends ## 4.0.1 - 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 ## 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) ## 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 ## 3.0.1 - 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]" ## 2.0.1 - Add replacements option e.g. [['|', 'or'], ['%', 'percent'], ['-', '_']] (@andriyor) ## 2.0.0 - Fix alternative dependency installation ## 1.2.6 - 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 ## 1.2.4 - Remove build artifacts during packaging - Simplify the setup.py file (@reece) ## 1.2.3 - Republish - possible corrupt 1.2.2 build ## 1.2.2 - 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 ## 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") ## 1.1.4 Bugfix: - Add more test cases, dropped `official` support for python 3.2 ## 1.1.3 Bugfix: - Handle unichar in python 3.x ## 1.1.2 Enhancement: - Ability to remove `stopwords` from string ## 1.0.2 Enhancement: - A new PyPI release ## 1.0.1 Enhancement: - Promoting to production grade ## 0.1.1 Enhancement: - 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 ## 0.0.9 Enhancement: - Enable console_scripts ## 0.0.8 Enhancement: - 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 ## 0.0.6 Enhancement: - Update for smart_truncate ## 0.0.5 Features: - 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) ## 0.0.3 Features: - Added the ability to truncate slugs (request by: juanriaza@github) ## 0.0.2 Enhancement: - Incremental update ## 0.0.1 - Initial version 07070100000009000081A400000000000000000000000163F8DECF0000044F000000000000000000000000000000000000001D00000000python-slugify-8.0.1/LICENSEThe MIT License Copyright (c) Val Neekman @ Neekware Inc. http://neekware.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 0707010000000A000081A400000000000000000000000163F8DECF00000037000000000000000000000000000000000000002100000000python-slugify-8.0.1/MANIFEST.ininclude LICENSE include README.md include CHANGELOG.md 0707010000000B000081A400000000000000000000000163F8DECF00001C1E000000000000000000000000000000000000001F00000000python-slugify-8.0.1/README.md# Python Slugify **A Python slugify application that handles unicode**. [![status-image]][status-link] [![version-image]][version-link] [![coverage-image]][coverage-link] # Overview **Best attempt** to create slugs from unicode strings while keeping it **DRY**. # Notice 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. ### `Official` Support Matrix | 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 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=(), allow_unicode=False ): """ 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 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 """ ``` # How to use ```python from slugify import slugify 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, allow_unicode=True) self.assertEqual(r, "影師嗎") 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 = 'Компьютер' 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=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 = '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 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 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 = '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") 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. # 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 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 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 `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]: 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 [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 [Neekware Inc.](http://neekware.com) 0707010000000C000081A400000000000000000000000163F8DECF0000002D000000000000000000000000000000000000002A00000000python-slugify-8.0.1/dev.requirements.txtpycodestyle==2.8.0 twine==3.4.1 flake8==4.0.10707010000000D000081ED00000000000000000000000163F8DECF000001A2000000000000000000000000000000000000001F00000000python-slugify-8.0.1/format.sh#!/bin/bash # Ignoring autogenerated files # -- Migration directories # Ignoring error codes # -- E128 continuation line under-indented for visual indent # -- E261 at least two spaces before inline comment # -- E225 missing whitespace around operator # -- E501 line too long # Ignoring warning codes # -- W605 invalid escape sequence '\d' pycodestyle --ignore=E128,E261,E225,E501,W605 slugify test.py setup.py 0707010000000E000081ED00000000000000000000000163F8DECF000009BE000000000000000000000000000000000000001E00000000python-slugify-8.0.1/setup.py#!/usr/bin/env python # Learn more: https://github.com/un33k/setup.py import os import sys from codecs import open from shutil import rmtree from setuptools import setup package = 'slugify' python_requires = ">=3.7" here = os.path.abspath(os.path.dirname(__file__)) 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', 'utf-8') as f: exec(f.read(), about) with open('README.md', 'r', 'utf-8') as f: readme = f.read() def status(s): print('\033[1m{0}\033[0m'.format(s)) # 'setup.py publish' shortcut. if sys.argv[-1] == 'publish': 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)) 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=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, 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.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']}, ) 0707010000000F000041ED00000000000000000000000263F8DECF00000000000000000000000000000000000000000000001D00000000python-slugify-8.0.1/slugify07070100000010000081A400000000000000000000000163F8DECF0000015A000000000000000000000000000000000000002900000000python-slugify-8.0.1/slugify/__init__.pyfrom .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__ 07070100000011000081A400000000000000000000000163F8DECF00000F1A000000000000000000000000000000000000002900000000python-slugify-8.0.1/slugify/__main__.pyfrom __future__ import print_function, absolute_import import argparse import sys from .slugify import slugify, DEFAULT_SEPARATOR def parse_args(argv): parser = argparse.ArgumentParser(description="Slug 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 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:]) 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, allow_unicode=args.allow_unicode ) 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() 07070100000012000081A400000000000000000000000163F8DECF00000145000000000000000000000000000000000000002C00000000python-slugify-8.0.1/slugify/__version__.py__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__ = '8.0.1' 07070100000013000081A400000000000000000000000163F8DECF000016A3000000000000000000000000000000000000002800000000python-slugify-8.0.1/slugify/slugify.pyimport re import sys import unicodedata from html.entities import name2codepoint try: import unidecode except ImportError: import text_unidecode as unidecode __all__ = ['slugify', 'smart_truncate'] 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'[\']+') 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 = '-' def smart_truncate(string, max_length=0, word_boundary=False, separator=' ', save_order=False): """ Truncate a string. :param string (str): string for modification :param max_length (int): output string length :param word_boundary (bool): :param save_order (bool): if True then word order of output string is like input string :param separator (str): separator between words :return: """ string = string.strip(separator) if not max_length: return string if len(string) < max_length: return string if not word_boundary: return string[:max_length].strip(separator) if separator not in string: return string[:max_length] truncated = '' for word in string.split(separator): if word: next_len = len(truncated) + len(word) if next_len < max_length: truncated += '{}{}'.format(word, separator) elif next_len == max_length: truncated += '{}'.format(word) break else: if save_order: break 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=(), allow_unicode=False): """ Make a slug from the given text. :param text (str): initial text :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 :param separator (str): separator between words :param stopwords (iterable): words to discount :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): """ # user-specific replacements if replacements: for old, new in replacements: text = text.replace(old, new) # ensure text is unicode 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 if not allow_unicode: text = unidecode.unidecode(text) # ensure text is still in unicode if not isinstance(text, str): text = str(text, 'utf-8', 'ignore') # character entity reference if entities: 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: chr(int(m.group(1))), text) except Exception: pass # hexadecimal character reference if hexadecimal: try: text = HEX_PATTERN.sub(lambda m: chr(int(m.group(1), 16)), text) except Exception: pass # translate if allow_unicode: text = unicodedata.normalize('NFKC', text) 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() # remove generated quotes -- post-process text = QUOTE_PATTERN.sub('', text) # cleanup numbers text = NUMBERS_PATTERN.sub('', text) # replace all other unwanted characters 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 text = DUPLICATE_DASH_PATTERN.sub(DEFAULT_SEPARATOR, text).strip(DEFAULT_SEPARATOR) # remove stopwords if stopwords: if lowercase: stopwords_lower = [s.lower() for s in stopwords] words = [w for w in text.split(DEFAULT_SEPARATOR) if w not in stopwords_lower] else: 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) if separator != DEFAULT_SEPARATOR: text = text.replace(DEFAULT_SEPARATOR, separator) return text 07070100000014000081A400000000000000000000000163F8DECF0000048F000000000000000000000000000000000000002800000000python-slugify-8.0.1/slugify/special.py# -*- coding: utf-8 -*- 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'), # sch (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 07070100000015000081A400000000000000000000000163F8DECF00005D35000000000000000000000000000000000000001D00000000python-slugify-8.0.1/test.py# -*- coding: utf-8 -*- import io 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 TestSlugify(unittest.TestCase): def test_extraneous_seperators(self): 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") def test_non_word_characters(self): txt = "This -- is a ## test ---" r = slugify(txt) self.assertEqual(r, "this-is-a-test") def test_phonetic_conversion_of_eastern_scripts(self): txt = '影師嗎' r = slugify(txt) self.assertEqual(r, "ying-shi-ma") def test_accented_text(self): 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") def test_accented_text_with_non_word_characters(self): txt = 'jaja---lol-méméméoo--a' r = slugify(txt) self.assertEqual(r, "jaja-lol-mememeoo-a") def test_cyrillic_text(self): txt = 'Компьютер' r = slugify(txt) self.assertEqual(r, "kompiuter") def test_max_length(self): 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) self.assertEqual(r, "jaja-lol-mememe") def test_max_length_cutoff_not_required(self): txt = 'jaja---lol-méméméoo--a' r = slugify(txt, max_length=50) self.assertEqual(r, "jaja-lol-mememeoo-a") def test_word_boundary(self): 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") def test_custom_separator(self): 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") def test_multi_character_separator(self): txt = 'jaja---lol-méméméoo--a' r = slugify(txt, max_length=20, word_boundary=True, separator="ZZZZZZ") self.assertEqual(r, "jajaZZZZZZlolZZZZZZmememeooZZZZZZa") def test_save_order(self): 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=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") def test_stopword_removal(self): txt = 'this has a stopword' r = slugify(txt, stopwords=['stopword']) self.assertEqual(r, 'this-has-a') def test_stopword_removal_casesensitive(self): txt = 'thIs Has a stopword Stopword' r = slugify(txt, stopwords=['Stopword'], lowercase=False) self.assertEqual(r, 'thIs-Has-a-stopword') def test_multiple_stopword_occurances(self): 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') def test_differently_cased_stopword_match(self): 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') def test_multiple_stopwords(self): 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') def test_stopwords_with_different_separator(self): txt = 'the quick brown fox jumps over the lazy dog' r = slugify(txt, 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) 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) self.assertEqual(r, '10-amazing-secrets') def test_contains_numbers(self): txt = 'buildings with 1000 windows' r = slugify(txt) self.assertEqual(r, 'buildings-with-1000-windows') def test_ends_with_number(self): txt = 'recipe number 3' r = slugify(txt) self.assertEqual(r, 'recipe-number-3') def test_numbers_only(self): txt = '404' r = slugify(txt) self.assertEqual(r, '404') def test_numbers_and_symbols(self): txt = '1,000 reasons you are #1' r = slugify(txt) self.assertEqual(r, '1000-reasons-you-are-1') def test_regex_pattern_keep_underscore(self): 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___") 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, 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") 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 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): 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) 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()) 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() 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!114 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor