Skip to content

Commit 3b51e23

Browse files
jdevera-hjun33k
authored andcommitted
Create command line tool that can set all parameters
1 parent e785874 commit 3b51e23

File tree

4 files changed

+205
-10
lines changed

4 files changed

+205
-10
lines changed

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,5 +67,5 @@ def get_version(package):
6767
extras_require=extras_require,
6868
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
6969
classifiers=classifiers,
70-
entry_points={'console_scripts': ['slugify=slugify.slugify:main']},
70+
entry_points={'console_scripts': ['slugify=slugify.__main__:entrypoint']},
7171
)

slugify/__main__.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
from __future__ import print_function, absolute_import
2+
import argparse
3+
import sys
4+
5+
from .slugify import slugify, DEFAULT_SEPARATOR
6+
7+
8+
def parse_args(argv):
9+
parser = argparse.ArgumentParser(description="Sluggify string")
10+
11+
input_group = parser.add_argument_group(description="Input")
12+
input_group.add_argument("input_string", nargs='*',
13+
help='Text to slugify')
14+
input_group.add_argument("--stdin", action='store_true',
15+
help="Take the text from STDIN")
16+
17+
parser.add_argument("--no-entities", action='store_false', dest='entities', default=True,
18+
help="Do not convert HTML entities to unicode")
19+
parser.add_argument("--no-decimal", action='store_false', dest='decimal', default=True,
20+
help="Do not convert HTML decimal to unicode")
21+
parser.add_argument("--no-hexadecimal", action='store_false', dest='hexadecimal', default=True,
22+
help="Do not convert HTML hexadecimal to unicode")
23+
parser.add_argument("--max-length", type=int, default=0,
24+
help="Output string length, 0 for no limit")
25+
parser.add_argument("--word-boundary", action='store_true', default=False,
26+
help="Truncate to complete word even if length ends up shorter than --max_length")
27+
parser.add_argument("--save-order", action='store_true', default=False,
28+
help="When set and --max_length > 0 return whole words in the initial order")
29+
parser.add_argument("--separator", type=str, default=DEFAULT_SEPARATOR,
30+
help="Separator between words. By default " + DEFAULT_SEPARATOR)
31+
parser.add_argument("--stopwords", nargs='+',
32+
help="Words to discount")
33+
parser.add_argument("--regex-pattern",
34+
help="Python regex pattern for allowed characters")
35+
parser.add_argument("--no-lowercase", action='store_false', dest='lowercase', default=True,
36+
help="Activate case sensitivity")
37+
parser.add_argument("--replacements", nargs='+',
38+
help="""Additional replacement rules e.g. "|->or", "%%->percent".""")
39+
40+
args = parser.parse_args(argv[1:])
41+
42+
if args.input_string and args.stdin:
43+
parser.error("Input strings and --stdin cannot work together")
44+
45+
if args.replacements:
46+
def split_check(repl):
47+
SEP = '->'
48+
if SEP not in repl:
49+
parser.error("Replacements must be of the form: ORIGINAL{SEP}REPLACED".format(SEP=SEP))
50+
return repl.split(SEP, 1)
51+
args.replacements = [split_check(repl) for repl in args.replacements]
52+
53+
if args.input_string:
54+
args.input_string = " ".join(args.input_string)
55+
elif args.stdin:
56+
args.input_string = sys.stdin.read()
57+
58+
if not args.input_string:
59+
args.input_string = ''
60+
61+
return args
62+
63+
64+
def slugify_params(args):
65+
return dict(
66+
text=args.input_string,
67+
entities=args.entities,
68+
decimal=args.decimal,
69+
hexadecimal=args.hexadecimal,
70+
max_length=args.max_length,
71+
word_boundary=args.word_boundary,
72+
save_order=args.save_order,
73+
separator=args.separator,
74+
stopwords=args.stopwords,
75+
lowercase=args.lowercase,
76+
replacements=args.replacements
77+
)
78+
79+
80+
def main(argv=None): # pragma: no cover
81+
""" Run this program """
82+
if argv is None:
83+
argv = sys.argv
84+
args = parse_args(argv)
85+
params = slugify_params(args)
86+
try:
87+
print(slugify(**params))
88+
except KeyboardInterrupt:
89+
sys.exit(-1)
90+
91+
92+
if __name__ == '__main__': # pragma: no cover
93+
main()

slugify/slugify.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,3 @@ def slugify(text, entities=True, decimal=True, hexadecimal=True, max_length=0, w
178178
text = text.replace(DEFAULT_SEPARATOR, separator)
179179

180180
return text
181-
182-
183-
def main(): # pragma: no cover
184-
if len(sys.argv) < 2:
185-
print("Usage %s TEXT TO SLUGIFY" % sys.argv[0])
186-
else:
187-
text = ' '.join(sys.argv[1:])
188-
print(slugify(text))

test.py

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
# -*- coding: utf-8 -*-
2-
2+
import io
3+
import os
4+
import sys
35
import unittest
6+
from contextlib import contextmanager
7+
48
from slugify import slugify
59
from slugify import smart_truncate
10+
from slugify.__main__ import slugify_params, parse_args
611

712

813
class TestSlugification(unittest.TestCase):
@@ -242,5 +247,110 @@ def test_smart_truncate_no_seperator(self):
242247
self.assertEqual(r, txt)
243248

244249

250+
PY3 = sys.version_info.major == 3
251+
252+
253+
@contextmanager
254+
def captured_stderr():
255+
backup = sys.stderr
256+
sys.stderr = io.StringIO() if PY3 else io.BytesIO()
257+
try:
258+
yield sys.stderr
259+
finally:
260+
sys.stderr = backup
261+
262+
263+
@contextmanager
264+
def loaded_stdin(contents):
265+
backup = sys.stdin
266+
sys.stdin = io.StringIO(contents) if PY3 else io.BytesIO(contents)
267+
try:
268+
yield sys.stdin
269+
finally:
270+
sys.stdin = backup
271+
272+
273+
class TestCommandParams(unittest.TestCase):
274+
DEFAULTS = {
275+
'entities': True,
276+
'decimal': True,
277+
'hexadecimal': True,
278+
'max_length': 0,
279+
'word_boundary': False,
280+
'save_order': False,
281+
'separator': '-',
282+
'stopwords': None,
283+
'lowercase': True,
284+
'replacements': None
285+
}
286+
287+
def get_params_from_cli(self, *argv):
288+
args = parse_args([None] + list(argv))
289+
return slugify_params(args)
290+
291+
def make_params(self, **values):
292+
return dict(self.DEFAULTS, **values)
293+
294+
def assertParamsMatch(self, expected, checked):
295+
reduced_checked = {}
296+
for key in expected.keys():
297+
reduced_checked[key] = checked[key]
298+
self.assertEqual(expected, reduced_checked)
299+
300+
def test_defaults(self):
301+
params = self.get_params_from_cli()
302+
self.assertParamsMatch(self.DEFAULTS, params)
303+
304+
def test_negative_flags(self):
305+
params = self.get_params_from_cli('--no-entities', '--no-decimal', '--no-hexadecimal', '--no-lowercase')
306+
expected = self.make_params(entities=False, decimal=False, hexadecimal=False, lowercase=False)
307+
self.assertFalse(expected['lowercase'])
308+
self.assertFalse(expected['word_boundary'])
309+
self.assertParamsMatch(expected, params)
310+
311+
def test_affirmative_flags(self):
312+
params = self.get_params_from_cli('--word-boundary', '--save-order')
313+
expected = self.make_params(word_boundary=True, save_order=True)
314+
self.assertParamsMatch(expected, params)
315+
316+
def test_valued_arguments(self):
317+
params = self.get_params_from_cli('--stopwords', 'abba', 'beatles', '--max-length', '98', '--separator', '+')
318+
expected = self.make_params(stopwords=['abba', 'beatles'], max_length=98, separator='+')
319+
self.assertParamsMatch(expected, params)
320+
321+
def test_replacements_right(self):
322+
params = self.get_params_from_cli('--replacements', 'A->B', 'C->D')
323+
expected = self.make_params(replacements=[['A', 'B'], ['C', 'D']])
324+
self.assertParamsMatch(expected, params)
325+
326+
def test_replacements_wrong(self):
327+
with self.assertRaises(SystemExit) as err, captured_stderr() as cse:
328+
self.get_params_from_cli('--replacements', 'A--B')
329+
self.assertEqual(err.exception.code, 2)
330+
self.assertIn("Replacements must be of the form: ORIGINAL->REPLACED", cse.getvalue())
331+
332+
def test_text_in_cli(self):
333+
params = self.get_params_from_cli('Cool Text')
334+
expected = self.make_params(text='Cool Text')
335+
self.assertParamsMatch(expected, params)
336+
337+
def test_text_in_cli_multi(self):
338+
params = self.get_params_from_cli('Cool', 'Text')
339+
expected = self.make_params(text='Cool Text')
340+
self.assertParamsMatch(expected, params)
341+
342+
def test_text_in_stdin(self):
343+
with loaded_stdin("Cool Stdin"):
344+
params = self.get_params_from_cli('--stdin')
345+
expected = self.make_params(text='Cool Stdin')
346+
self.assertParamsMatch(expected, params)
347+
348+
def test_two_text_sources_fails(self):
349+
with self.assertRaises(SystemExit) as err, captured_stderr() as cse:
350+
self.get_params_from_cli('--stdin', 'Text')
351+
self.assertEqual(err.exception.code, 2)
352+
self.assertIn("Input strings and --stdin cannot work together", cse.getvalue())
353+
354+
245355
if __name__ == '__main__':
246356
unittest.main()

0 commit comments

Comments
 (0)