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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy