GeezerVison is showing github.com — fixed roman_range (descending generation) + better docs · daveoncode/python-string-utils@e860da8
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: README.md +9Lines changed: 9 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -331,3 +331,12 @@ secure_random_hex(12) | 331 | 331 |
| 332 | 332 | Full API documentation available on: http://python-string-utils.readthedocs.org/en/latest/ | 333 | 333 |
| | 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> |
Collapse file Copy file name to clipboard +20Lines changed: 20 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 %} |
Collapse file Copy file name to clipboardExpand all lines: docs/index.rst -1Lines changed: 0 additions & 1 deletion - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -32,7 +32,6 @@ The library basically consists in the python package `string_utils`, containing | 32 | 32 | Plus a secondary package `tests` which includes several submodules. | 33 | 33 | Specifically one for each test suite and named according to the api to test (eg. tests for `is_ip()` | 34 | 34 | will be in `test_is_ip.py` and so on). | 35 | | - | 36 | 35 | All the public API are importable directly from the main package `string_utils`, so this: | 37 | 36 |
| 38 | 37 | >>> from string_utils.validation import is_ip | |
Collapse file Copy file name to clipboardExpand all lines: string_utils/generation.py +34-11Lines changed: 34 additions & 11 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -19,9 +19,10 @@ def uuid(as_hex: bool = False) -> str: | 19 | 19 | """ | 20 | 20 | Generated an UUID string (using `uuid.uuid4()`). | 21 | 21 |
| 22 | | - *Example:* | | 22 | + *Examples:* | 23 | 23 |
| 24 | 24 | >>> uuid() # possible output: '97e3a716-6b33-4ab9-9bb1-8128cb24d76b' | | 25 | + >>> uuid(as_hex=True) # possible output: '97e3a7166b334ab99bb18128cb24d76b' | 25 | 26 |
| 26 | 27 | :param as_hex: True to return the hex value of the UUID, False to get its default representation (default). | 27 | 28 | :return: uuid string. | @@ -60,7 +61,7 @@ def secure_random_hex(byte_count: int) -> str: | 60 | 61 | """ | 61 | 62 | Generates a random string using secure low level random generator (os.urandom). | 62 | 63 |
| 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\ | 64 | 65 | the double of the given `byte_count`. | 65 | 66 |
| 66 | 67 | *Example:* | @@ -88,27 +89,49 @@ def roman_range(stop: int, start: int = 1, step: int = 1) -> Generator: | 88 | 89 |
| 89 | 90 | *Example:* | 90 | 91 |
| 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 | 92 | 96 |
| 93 | 97 | :param stop: Number at which the generation must stop (must be <= 3999). | 94 | 98 | :param start: Number at which the generation must start (must be >= 1). | 95 | 99 | :param step: Increment of each generation step (default to 1). | 96 | 100 | :return: Generator of roman numbers. | 97 | 101 | """ | 98 | 102 |
| 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) | 102 | 114 |
| 103 | 115 | 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 | 105 | 122 |
| 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) | 109 | 125 |
| | 126 | + # checks each single argument value | 110 | 127 | validate(stop, 'stop') | 111 | 128 | 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') | 113 | 136 |
| 114 | 137 | return generate() |
Collapse file Copy file name to clipboardExpand all lines: string_utils/manipulation.py +32-12Lines changed: 32 additions & 12 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -404,24 +404,24 @@ def strip_html(input_string: str, keep_tag_content: bool = False) -> str: | 404 | 404 |
| 405 | 405 | def prettify(input_string: str) -> str: | 406 | 406 | """ | 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: | 408 | 408 |
| 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 | 414 | 414 | - Text inside double quotes cannot start or end with spaces, but one, and only one space must come first and \ | 415 | 415 | after quotes (foo" bar"baz -> foo "bar" baz) | 416 | 416 | - 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%") | 419 | 419 | - Saxon genitive is correct ("Dave' s dog" -> "Dave's dog") | 420 | 420 |
| 421 | 421 | *Examples:* | 422 | 422 |
| 423 | 423 | >>> 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!' | 425 | 425 |
| 426 | 426 | :param input_string: String to manipulate | 427 | 427 | :return: Prettified string. | @@ -435,7 +435,7 @@ def asciify(input_string: str) -> str: | 435 | 435 | Force string content to be ascii-only by translating all non-ascii chars into the closest possible representation | 436 | 436 | (eg: ó -> o, Ë -> E, ç -> c...). | 437 | 437 |
| 438 | | - Some chars may be lost if impossible to translate. | | 438 | + **Bear in mind**: Some chars may be lost if impossible to translate. | 439 | 439 |
| 440 | 440 | *Example:* | 441 | 441 |
| @@ -500,16 +500,21 @@ def slugify(input_string: str, separator: str = '-') -> str: | 500 | 500 | def booleanize(input_string: str) -> bool: | 501 | 501 | """ | 502 | 502 | Turns a string into a boolean based on its content (CASE INSENSITIVE). | | 503 | +
| 503 | 504 | A positive boolean (True) is returned if the string value is one of the following: | | 505 | +
| 504 | 506 | - "true" | 505 | 507 | - "1" | 506 | 508 | - "yes" | 507 | 509 | - "y" | | 510 | +
| 508 | 511 | Otherwise False is returned. | 509 | 512 |
| 510 | | - *Example:* | | 513 | + *Examples:* | 511 | 514 |
| 512 | 515 | >>> booleanize('true') # returns True | | 516 | + >>> booleanize('YES') # returns True | | 517 | + >>> booleanize('nope') # returns False | 513 | 518 |
| 514 | 519 | :param input_string: String to convert | 515 | 520 | :type input_string: str | @@ -525,6 +530,20 @@ def strip_margin(input_string: str) -> str: | 525 | 530 | """ | 526 | 531 | Removes tab indentation from multi line strings (inspired by analogous Scala function). | 527 | 532 |
| | 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 | +
| 528 | 547 | :param input_string: String to format | 529 | 548 | :type input_string: str | 530 | 549 | :return: A string without left margins | @@ -559,7 +578,7 @@ def compress(input_string: str, encoding: str = 'utf-8', compression_level: int | 559 | 578 |
| 560 | 579 | *Examples:* | 561 | 580 |
| 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) | 563 | 582 | >>> # "original" will be a string with 169 chars: | 564 | 583 | >>> original = ' '.join(['word n{}'.format(n) for n in range(20)]) | 565 | 584 | >>> # "compressed" will be a string of 88 chars | @@ -592,6 +611,7 @@ def decompress(input_string: str, encoding: str = 'utf-8') -> str: | 592 | 611 | def roman_encode(input_number: Union[str, int]) -> str: | 593 | 612 | """ | 594 | 613 | Convert the given number/string into a roman number. | | 614 | +
| 595 | 615 | The passed input must represents a positive integer in the range 1-3999 (inclusive). | 596 | 616 |
| 597 | 617 | Why this limit? You may be wondering: | |
Collapse file Copy file name to clipboardExpand all lines: string_utils/validation.py +22-23Lines changed: 22 additions & 23 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -204,11 +204,11 @@ def is_email(input_string: Any) -> bool: | 204 | 204 | """ | 205 | 205 | Check if a string is an email. | 206 | 206 |
| 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 \ | 208 | 208 | email address, but instead it's based on real world cases in order to match more than 99% \ | 209 | 209 | of emails and catch user mistakes. For example the percentage sign "%" is a valid sign for an email, \ | 210 | 210 | 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. | 212 | 212 |
| 213 | 213 | *Examples:* | 214 | 214 |
| @@ -225,22 +225,21 @@ def is_email(input_string: Any) -> bool: | 225 | 225 | def is_credit_card(input_string: Any, card_type: str = None) -> bool: | 226 | 226 | """ | 227 | 227 | 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, | 229 | 229 | otherwise any known credit card number will be accepted. | 230 | 230 |
| 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: | 234 | 232 |
| 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 | 243 | 239 |
| | 240 | + :param input_string: String to check. | | 241 | + :type input_string: str | | 242 | + :param card_type: Card type. Default to None (any card). | 244 | 243 | :type card_type: str | 245 | 244 |
| 246 | 245 | :return: True if credit card, false otherwise. | @@ -290,10 +289,9 @@ def is_snake_case(input_string: Any, separator: str = '_') -> bool: | 290 | 289 |
| 291 | 290 | A string is considered snake case when: | 292 | 291 |
| 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 | 297 | 295 |
| 298 | 296 | *Examples:* | 299 | 297 |
| @@ -511,7 +509,7 @@ def is_isogram(input_string: Any) -> bool: | 511 | 509 |
| 512 | 510 | def is_slug(input_string: Any, sign: str = '-') -> bool: | 513 | 511 | """ | 514 | | - Checks if a given string is a slug. | | 512 | + Checks if a given string is a slug (as created by `slugify()`). | 515 | 513 |
| 516 | 514 | *Examples:* | 517 | 515 |
| @@ -534,9 +532,10 @@ def is_slug(input_string: Any, sign: str = '-') -> bool: | 534 | 532 |
| 535 | 533 | def contains_html(input_string: str) -> bool: | 536 | 534 | """ | 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. | 540 | 539 |
| 541 | 540 | *Examples:* | 542 | 541 |
| @@ -565,7 +564,7 @@ def words_count(input_string: str) -> int: | 565 | 564 | *Examples:* | 566 | 565 |
| 567 | 566 | >>> 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 | 569 | 568 |
| 570 | 569 | :param input_string: String to check. | 571 | 570 | :type input_string: str | |
|