Skip to content

Commit e860da8

Browse files
committed
fixed roman_range (descending generation) + better docs
1 parent 947ceb1 commit e860da8

File tree

7 files changed

+169
-50
lines changed

7 files changed

+169
-50
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,3 +331,12 @@ secure_random_hex(12)
331331
332332
Full API documentation available on: http://python-string-utils.readthedocs.org/en/latest/
333333
334+
335+
## Support the project!
336+
337+
Do you like this project? Would you like to see it updated more often with new features and improvements?
338+
If so, you can make a small donation by clicking the button down below, it would be really appreciated! :)
339+
340+
<a href="https://www.buymeacoffee.com/c4yYUvp" target="_blank">
341+
<img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" width="217" height="51" />
342+
</a>

docs/_templates/layout.html

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{% extends '!layout.html' %}
2+
3+
{% block document %}
4+
5+
{{ super() }}
6+
7+
<div style="padding: 30px 0 40px 0; border-top: 1px solid #ccc; margin-top: 30px">
8+
<h1>Support the project!</h1>
9+
10+
<p>
11+
Do you like this project? Would you like to see it updated more often with new features and improvements?
12+
If so, you can make a small donation by clicking the button down below, it would be really appreciated! :)
13+
</p>
14+
15+
<a href="https://www.buymeacoffee.com/c4yYUvp" target="_blank">
16+
<img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" width="217" height="51" />
17+
</a>
18+
</div>
19+
20+
{% endblock %}

docs/index.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ The library basically consists in the python package `string_utils`, containing
3232
Plus a secondary package `tests` which includes several submodules.
3333
Specifically one for each test suite and named according to the api to test (eg. tests for `is_ip()`
3434
will be in `test_is_ip.py` and so on).
35-
3635
All the public API are importable directly from the main package `string_utils`, so this:
3736

3837
>>> from string_utils.validation import is_ip

string_utils/generation.py

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ def uuid(as_hex: bool = False) -> str:
1919
"""
2020
Generated an UUID string (using `uuid.uuid4()`).
2121
22-
*Example:*
22+
*Examples:*
2323
2424
>>> uuid() # possible output: '97e3a716-6b33-4ab9-9bb1-8128cb24d76b'
25+
>>> uuid(as_hex=True) # possible output: '97e3a7166b334ab99bb18128cb24d76b'
2526
2627
:param as_hex: True to return the hex value of the UUID, False to get its default representation (default).
2728
:return: uuid string.
@@ -60,7 +61,7 @@ def secure_random_hex(byte_count: int) -> str:
6061
"""
6162
Generates a random string using secure low level random generator (os.urandom).
6263
63-
BEAR IN MIND: due to hex conversion, the returned string will have a size that is exactly\
64+
**Bear in mind**: due to hex conversion, the returned string will have a size that is exactly\
6465
the double of the given `byte_count`.
6566
6667
*Example:*
@@ -88,27 +89,49 @@ def roman_range(stop: int, start: int = 1, step: int = 1) -> Generator:
8889
8990
*Example:*
9091
91-
>>> for n in roman_range(7): print(n) # prints: I, II, III, IV, V, VI, VII
92+
>>> for n in roman_range(7): print(n)
93+
>>> # prints: I, II, III, IV, V, VI, VII
94+
>>> for n in roman_range(start=7, stop=1, step=-1): print(n)
95+
>>> # prints: VII, VI, V, IV, III, II, I
9296
9397
:param stop: Number at which the generation must stop (must be <= 3999).
9498
:param start: Number at which the generation must start (must be >= 1).
9599
:param step: Increment of each generation step (default to 1).
96100
:return: Generator of roman numbers.
97101
"""
98102

99-
def validate(arg_value, arg_name):
100-
if not isinstance(arg_value, int) or (arg_value < 1 or arg_value > 3999):
101-
raise ValueError('"{}" must be an integer in the range 1-3999'.format(arg_name))
103+
def validate(arg_value, arg_name, allow_negative=False):
104+
msg = '"{}" must be an integer in the range 1-3999'.format(arg_name)
105+
106+
if not isinstance(arg_value, int):
107+
raise ValueError(msg)
108+
109+
if allow_negative:
110+
arg_value = abs(arg_value)
111+
112+
if arg_value < 1 or arg_value > 3999:
113+
raise ValueError(msg)
102114

103115
def generate():
104-
current_step = start
116+
current = start
117+
118+
# generate values for each step
119+
while current != stop:
120+
yield roman_encode(current)
121+
current += step
105122

106-
while current_step < stop + 1:
107-
yield roman_encode(current_step)
108-
current_step += step
123+
# last value to return
124+
yield roman_encode(current)
109125

126+
# checks each single argument value
110127
validate(stop, 'stop')
111128
validate(start, 'start')
112-
validate(step, 'step')
129+
validate(step, 'step', allow_negative=True)
130+
131+
# checks if the provided configuration leads to a feasible iteration with respect to boundaries or not
132+
forward_exceed = step > 0 and (start > stop or start + step > stop)
133+
backward_exceed = step < 0 and (start < stop or start + step < stop)
134+
if forward_exceed or backward_exceed:
135+
raise OverflowError('Invalid start/stop/step configuration')
113136

114137
return generate()

string_utils/manipulation.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -404,24 +404,24 @@ def strip_html(input_string: str, keep_tag_content: bool = False) -> str:
404404

405405
def prettify(input_string: str) -> str:
406406
"""
407-
Turns an ugly text string into a beautiful one by applying a regex pipeline which ensures the following:
407+
Reformat a string by applying the following basic grammar and formatting rules:
408408
409-
- String cannot start or end with spaces\
410-
- String cannot have multiple sequential spaces, empty lines or punctuation (except for "?", "!" and ".")\
411-
- Arithmetic operators (+, -, /, \\*, =) must have one, and only one space before and after themselves\
412-
- The first letter after a dot, an exclamation or a question mark must be uppercase\
413-
- One, and only one space should follow a dot, an exclamation or a question mark\
409+
- String cannot start or end with spaces
410+
- The first letter in the string and the ones after a dot, an exclamation or a question mark must be uppercase
411+
- String cannot have multiple sequential spaces, empty lines or punctuation (except for "?", "!" and ".")
412+
- Arithmetic operators (+, -, /, \\*, =) must have one, and only one space before and after themselves
413+
- One, and only one space should follow a dot, a comma, an exclamation or a question mark
414414
- Text inside double quotes cannot start or end with spaces, but one, and only one space must come first and \
415415
after quotes (foo" bar"baz -> foo "bar" baz)
416416
- Text inside round brackets cannot start or end with spaces, but one, and only one space must come first and \
417-
after brackets ("foo(bar )baz" -> "foo (bar) baz")\
418-
- Percentage sign ("%") cannot be preceded by a space if there is a number before ("100 %" -> "100%")\
417+
after brackets ("foo(bar )baz" -> "foo (bar) baz")
418+
- Percentage sign ("%") cannot be preceded by a space if there is a number before ("100 %" -> "100%")
419419
- Saxon genitive is correct ("Dave' s dog" -> "Dave's dog")
420420
421421
*Examples:*
422422
423423
>>> prettify(' unprettified string ,, like this one,will be"prettified" .it\\' s awesome! ')
424-
>>> # the ouput will be: 'Unprettified string, like this one, will be "prettified". It\'s awesome!'
424+
>>> # -> 'Unprettified string, like this one, will be "prettified". It\'s awesome!'
425425
426426
:param input_string: String to manipulate
427427
:return: Prettified string.
@@ -435,7 +435,7 @@ def asciify(input_string: str) -> str:
435435
Force string content to be ascii-only by translating all non-ascii chars into the closest possible representation
436436
(eg: ó -> o, Ë -> E, ç -> c...).
437437
438-
Some chars may be lost if impossible to translate.
438+
**Bear in mind**: Some chars may be lost if impossible to translate.
439439
440440
*Example:*
441441
@@ -500,16 +500,21 @@ def slugify(input_string: str, separator: str = '-') -> str:
500500
def booleanize(input_string: str) -> bool:
501501
"""
502502
Turns a string into a boolean based on its content (CASE INSENSITIVE).
503+
503504
A positive boolean (True) is returned if the string value is one of the following:
505+
504506
- "true"
505507
- "1"
506508
- "yes"
507509
- "y"
510+
508511
Otherwise False is returned.
509512
510-
*Example:*
513+
*Examples:*
511514
512515
>>> booleanize('true') # returns True
516+
>>> booleanize('YES') # returns True
517+
>>> booleanize('nope') # returns False
513518
514519
:param input_string: String to convert
515520
:type input_string: str
@@ -525,6 +530,20 @@ def strip_margin(input_string: str) -> str:
525530
"""
526531
Removes tab indentation from multi line strings (inspired by analogous Scala function).
527532
533+
*Example:*
534+
535+
>>> strip_margin('''
536+
>>> line 1
537+
>>> line 2
538+
>>> line 3
539+
>>> ''')
540+
>>> # returns:
541+
>>> '''
542+
>>> line 1
543+
>>> line 2
544+
>>> line 3
545+
>>> '''
546+
528547
:param input_string: String to format
529548
:type input_string: str
530549
:return: A string without left margins
@@ -559,7 +578,7 @@ def compress(input_string: str, encoding: str = 'utf-8', compression_level: int
559578
560579
*Examples:*
561580
562-
>>> n = 0 # fix for Pycharm (not fixable using ignore comments)... ignore it
581+
>>> n = 0 # <- ignore this, it's a fix for Pycharm (not fixable using ignore comments)
563582
>>> # "original" will be a string with 169 chars:
564583
>>> original = ' '.join(['word n{}'.format(n) for n in range(20)])
565584
>>> # "compressed" will be a string of 88 chars
@@ -592,6 +611,7 @@ def decompress(input_string: str, encoding: str = 'utf-8') -> str:
592611
def roman_encode(input_number: Union[str, int]) -> str:
593612
"""
594613
Convert the given number/string into a roman number.
614+
595615
The passed input must represents a positive integer in the range 1-3999 (inclusive).
596616
597617
Why this limit? You may be wondering:

string_utils/validation.py

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,11 @@ def is_email(input_string: Any) -> bool:
204204
"""
205205
Check if a string is an email.
206206
207-
By design, the implementation of this checking does not follow the specification for a valid \
207+
By design, the implementation of this checking does not strictly follow the specification for a valid \
208208
email address, but instead it's based on real world cases in order to match more than 99% \
209209
of emails and catch user mistakes. For example the percentage sign "%" is a valid sign for an email, \
210210
but actually no one use it, instead if such sign is found in a string coming from user input (like a \
211-
web form) is very likely that the intention was to type "5" (which is on the same key on a US keyboard).
211+
web form) it's very likely that it's a mistake.
212212
213213
*Examples:*
214214
@@ -225,22 +225,21 @@ def is_email(input_string: Any) -> bool:
225225
def is_credit_card(input_string: Any, card_type: str = None) -> bool:
226226
"""
227227
Checks if a string is a valid credit card number.
228-
If card type is provided then it checks that specific type,
228+
If card type is provided then it checks against that specific type only,
229229
otherwise any known credit card number will be accepted.
230230
231-
:param input_string: String to check.
232-
:type input_string: str
233-
:param card_type: Card type. Can be one of these:
231+
Supported card types are the following:
234232
235-
* VISA
236-
* MASTERCARD
237-
* AMERICAN_EXPRESS
238-
* DINERS_CLUB
239-
* DISCOVER
240-
* JCB
241-
242-
or None. Default to None (any card).
233+
- VISA
234+
- MASTERCARD
235+
- AMERICAN_EXPRESS
236+
- DINERS_CLUB
237+
- DISCOVER
238+
- JCB
243239
240+
:param input_string: String to check.
241+
:type input_string: str
242+
:param card_type: Card type. Default to None (any card).
244243
:type card_type: str
245244
246245
:return: True if credit card, false otherwise.
@@ -290,10 +289,9 @@ def is_snake_case(input_string: Any, separator: str = '_') -> bool:
290289
291290
A string is considered snake case when:
292291
293-
* it's composed only by lowercase letters ([a-z]), underscores (or provided separator) \
294-
and optionally numbers ([0-9])
295-
* it does not start/end with an underscore (or provided separator)
296-
* it does not start with a number
292+
- it's composed only by lowercase/uppercase letters and digits
293+
- it contains at least one underscore (or provided separator)
294+
- it does not start with a number
297295
298296
*Examples:*
299297
@@ -511,7 +509,7 @@ def is_isogram(input_string: Any) -> bool:
511509

512510
def is_slug(input_string: Any, sign: str = '-') -> bool:
513511
"""
514-
Checks if a given string is a slug.
512+
Checks if a given string is a slug (as created by `slugify()`).
515513
516514
*Examples:*
517515
@@ -534,9 +532,10 @@ def is_slug(input_string: Any, sign: str = '-') -> bool:
534532

535533
def contains_html(input_string: str) -> bool:
536534
"""
537-
Checks if the given string contains html code.
538-
By design, this function is very permissive regarding what to consider html code, don't expect to use it
539-
as an html validator, its goal is to detect "malicious" or undesired html tags in the text.
535+
Checks if the given string contains HTML/XML tags.
536+
537+
By design, this function matches ANY type of tag, so don't expect to use it
538+
as an HTML validator, its goal is to detect "malicious" or undesired tags in the text.
540539
541540
*Examples:*
542541
@@ -565,7 +564,7 @@ def words_count(input_string: str) -> int:
565564
*Examples:*
566565
567566
>>> words_count('hello world') # returns 2
568-
>>> words_count('one,two,three') # returns 3 (no need for spaces, punctuation is recognized!)
567+
>>> words_count('one,two,three.stop') # returns 4
569568
570569
:param input_string: String to check.
571570
:type input_string: str

0 commit comments

Comments
 (0)