summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVal Neekman <val@neekware.com>2020-06-29 23:08:38 -0400
committerVal Neekman <val@neekware.com>2020-06-29 23:08:38 -0400
commitbdf56c4138ae6693c34cc7b4ce4b77514190d724 (patch)
tree321ef5a10b2962cc1ed7c2f34a93db045d975d87
parentbcf1d9bbe33408e6c06a5edeff6945b453bb4097 (diff)
parentd1599a799d551d6bf57b1a727af10c125fc6a257 (diff)
downloadpython-slugify-bdf56c4138ae6693c34cc7b4ce4b77514190d724.tar.gz
Merge branch 'master' into sandbox
-rw-r--r--README.md30
-rwxr-xr-xsetup.py2
-rw-r--r--slugify/__main__.py93
-rw-r--r--slugify/slugify.py8
-rw-r--r--test.py119
5 files changed, 240 insertions, 12 deletions
diff --git a/README.md b/README.md
index 239f263..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
====================
@@ -165,8 +191,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
diff --git a/setup.py b/setup.py
index 00a7f4b..506f77b 100755
--- a/setup.py
+++ b/setup.py
@@ -67,5 +67,5 @@ setup(
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__:main']},
)
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..ddf1bf4 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,117 @@ class TestUtils(unittest.TestCase):
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()