Skip to content

Commit 0f6e635

Browse files
committed
Applied minor improvements
1 parent 31406e1 commit 0f6e635

4 files changed

Lines changed: 50 additions & 38 deletions

File tree

codext/__common__.py

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,22 @@
1919
from six import binary_type, string_types, text_type, BytesIO
2020
from string import *
2121
from types import FunctionType, ModuleType
22-
try: # Python3
23-
from importlib import reload
24-
except ImportError:
25-
pass
26-
try: # Python3
27-
from inspect import getfullargspec
28-
except ImportError:
29-
from inspect import getargspec as getfullargspec
3022
try: # Python 2
23+
import __builtin__ as builtins
24+
from inspect import getargspec as getfullargspec
3125
from string import maketrans
3226
except ImportError: # Python 3
27+
import builtins
28+
from importlib import reload
29+
from inspect import getfullargspec
3330
maketrans = str.maketrans
3431

3532

3633
__all__ = ["add", "add_macro", "add_map", "b", "clear", "codecs", "decode", "encode", "ensure_str", "examples", "guess",
3734
"isb", "generate_strings_from_regex", "get_alphabet_from_mask", "handle_error", "is_native",
3835
"list_categories", "list_encodings", "list_macros", "lookup", "maketrans", "os", "rank", "re", "register",
3936
"remove", "reset", "s2i", "search", "stopfunc", "BytesIO", "_input", "_stripl", "CodecMacro",
40-
"ParameterError", "DARWIN", "LANG", "LINUX", "MASKS", "PY3", "UNIX", "WINDOWS"]
37+
"DARWIN", "LANG", "LINUX", "MASKS", "PY3", "UNIX", "WINDOWS"]
4138
CODECS_REGISTRY = None
4239
CODECS_OVERWRITTEN = []
4340
CODECS_CATEGORIES = ["native", "custom"]
@@ -75,6 +72,7 @@
7572
fix = lambda x, ref: b(x) if isb(ref) else ensure_str(x) if iss(ref) else x
7673

7774
s2i = lambda s: int(codecs.encode(s, "base16"), 16)
75+
exc_name = lambda e: "".join(t.capitalize() for t in re.split(r"[-_+]", e))
7876

7977

8078
class CodecMacro(tuple):
@@ -145,9 +143,6 @@ def __repr__(self):
145143
return "<codext.CodecMacro object for encoding %s at %#x>" % (self.name, id(self))
146144

147145

148-
class ParameterError(ValueError):
149-
__module__ = Exception.__module__
150-
151146
# inspired from: https://stackoverflow.com/questions/10875442/possible-to-change-a-functions-repr-in-python
152147
class Repr(object):
153148
def __init__(self, name, func):
@@ -185,6 +180,14 @@ def _input(infile):
185180
return c
186181

187182

183+
def _set_exc(name, etype="ValueError"):
184+
if not hasattr(builtins, name):
185+
exec("class %s(%s): __module__ = 'builtins'" % (name, etype))
186+
setattr(builtins, name, locals()[name])
187+
_set_exc("InputSizeLimitError")
188+
_set_exc("ParameterError")
189+
190+
188191
def _stripl(s, st_lines, st_crlf):
189192
if st_crlf:
190193
s = s.replace(b"\r\n", b"") if isb(s) else s.replace("\r\n", "")
@@ -213,12 +216,18 @@ def add(ename, encode=None, decode=None, pattern=None, text=True, add_to_codecs=
213216
to remove the codec later
214217
"""
215218
remove(ename)
216-
if encode and not isinstance(encode, FunctionType):
217-
raise ValueError("Bad 'encode' function")
218-
if decode and not isinstance(decode, FunctionType):
219-
raise ValueError("Bad 'decode' function")
219+
if encode:
220+
if not isinstance(encode, FunctionType):
221+
raise ValueError("Bad 'encode' function")
222+
_set_exc("%sEncodeError" % exc_name(ename)) # create the custom encode exception as a builtin
223+
if decode:
224+
if not isinstance(decode, FunctionType):
225+
raise ValueError("Bad 'decode' function")
226+
_set_exc("%sDecodeError" % exc_name(ename)) # create the custom decode exception as a builtin
220227
if not encode and not decode:
221228
raise ValueError("At least one en/decoding function must be defined")
229+
for exc in kwargs.get('extra_exceptions', []):
230+
_set_exc(exc) # create additional custom exceptions as builtins
222231
glob = currentframe().f_back.f_globals
223232
# search function for the new encoding
224233
@_with_repr(ename)
@@ -516,7 +525,8 @@ def code(text, errors="strict"):
516525
text = "".join(str(ord(c)).zfill(3) for c in text)
517526
r = ""
518527
lsep = "" if decode else sep if len(sep) <= 1 else sep[0]
519-
error_func = handle_error(ename, errors, lsep, repl_char, rminlen, decode)
528+
kind = ["character", "token"][tmaxlen > 1]
529+
error_func = handle_error(ename, errors, lsep, repl_char, rminlen, decode, kind)
520530

521531
# get the value from the mapping dictionary, trying the token with its inverted case if relevant
522532
def __get_value(token, position, case_changed=False):
@@ -722,6 +732,11 @@ def remove(name):
722732
json.dump(PERS_MACROS, f, indent=2)
723733
except KeyError:
724734
pass
735+
for s in ["En", "De"]:
736+
try:
737+
delattr(builtins, "%s%scodeError" % (name.capitalize(), s))
738+
except AttributeError:
739+
pass
725740
codecs.remove = remove
726741

727742

@@ -815,7 +830,7 @@ def get_alphabet_from_mask(mask):
815830

816831

817832
# generic error handling function
818-
def handle_error(ename, errors, sep="", repl_char="?", repl_minlen=1, decode=False, item="position"):
833+
def handle_error(ename, errors, sep="", repl_char="?", repl_minlen=1, decode=False, kind="character", item="position"):
819834
""" This shortcut function allows to handle error modes given some tuning parameters.
820835
821836
:param ename: encoding name
@@ -826,23 +841,21 @@ def handle_error(ename, errors, sep="", repl_char="?", repl_minlen=1, decode=Fal
826841
:param decode: whether we are encoding or decoding
827842
:param item: position item description (for describing the error ; e.g. "group" or "token")
828843
"""
829-
name = "".join(t.capitalize() for t in re.split(r"[-_+]", ename))
830-
# dynamically make dedicated exception classes bound to the related codec module
831-
exc = "%s%scodeError" % (name, ["En", "De"][decode])
832-
glob = {'__name__': "__main__"}
833-
exec("class %s(ValueError): pass" % exc, glob)
834-
835-
def _handle_error(token, position, output=""):
844+
exc = "%s%scodeError" % (exc_name(ename), ["En", "De"][decode])
845+
846+
def _handle_error(token, position, output="", eename=None):
836847
""" This handles an encoding/decoding error according to the selected handling mode.
837848
838849
:param token: input token to be encoded/decoded
839850
:param position: token position index
840851
:param output: output, as decoded up to the position of the error
841852
"""
842853
if errors == "strict":
843-
msg = "'{}' codec can't {}code character '{}' in {} {}"
844-
err = glob[exc](msg.format(ename, ["en", "de"][decode], token, item, position))
854+
msg = "'%s' codec can't %scode %s '%s' in %s %d"
855+
token = token[:7] + "..." if len(token) > 10 else token
856+
err = getattr(builtins, exc)(msg % (eename or ename, ["en", "de"][decode], kind, token, item, position))
845857
err.output = output
858+
err.__cause__ = err
846859
raise err
847860
elif errors == "leave":
848861
return token + sep

codext/common/cases.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,23 @@
1515

1616

1717
pascal = lambda i, e="strict": ("".join(x.capitalize() for x in re.findall(r"[0-9a-z]+", i.lower())), len(i))
18-
add("camel", lambda i, e: uncapitalize(pascal(i, e)[0]), None, r"^camel(?:[-_]?case)?$")
19-
add("pascal", pascal, None, r"^pascal(?:[-_]?case)?$")
18+
add("camelcase", lambda i, e="strict": uncapitalize(pascal(i, e)[0]), None, r"^camel(?:[-_]?case)?$")
19+
add("pascalcase", pascal, None, r"^pascal(?:[-_]?case)?$")
2020

2121
capitalize = lambda i, e="strict": (i.capitalize(), len(i))
2222
uncapitalize = lambda i, e="strict": (i[0].lower() + i[1:] if len(i) > 0 else "", len(i))
2323
add("capitalize", capitalize, uncapitalize)
2424

25-
lowercase = lambda i, e="strict": (i.lower(), len(i))
26-
uppercase = lambda i, e="strict": (i.upper(), len(i))
25+
lowercase, uppercase = lambda i, e="strict": (i.lower(), len(i)), lambda i, e="strict": (i.upper(), len(i))
2726
add("uppercase", uppercase, lowercase, r"^upper(?:case)?$")
2827
add("lowercase", lowercase, uppercase, r"^lower(?:case)?$")
2928

3029
slugify = lambda i, e="strict", d="-": (re.sub(r"[^0-9a-z]+", d, i.lower()).strip(d), len(i))
31-
add("slugify", lambda i, e: slugify(i, e), None, r"^(?:slug(?:ify)?|kebab(?:[-_]?case)?)$")
32-
add("snake", lambda i, e: slugify(i, e, "_"), None, r"^snake(?:[-_]?case)$")
30+
add("slugify", lambda i, e="strict": slugify(i, e), None, r"^(?:slug(?:ify)?|kebab(?:[-_]?case)?)$")
31+
add("snakecase", lambda i, e="strict": slugify(i, e, "_"), None, r"^snake(?:[-_]?case)$")
3332

3433
swapcase = lambda i, e="strict": (i.swapcase(), len(i))
35-
add("swapcase", swapcase, swapcase, r"^swap(?:[-_]?case)?$")
34+
add("swapcase", swapcase, swapcase, r"^(?:swap(?:[-_]?case)?|invert(?:case)?)$")
3635

3736
title = lambda i, e="strict": (i.title(), len(i))
3837
untitle = lambda i, e="strict": (" ".join(w[0].lower() + w[1:] if len(w) > 0 else "" for w in i.split()), len(i))

codext/common/dummy.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
"""
1212
import re
1313

14-
from ..__common__ import add
14+
from ..__common__ import *
1515

1616

1717
def replace(pair, *args):
18-
def code(input, error="strict"):
18+
def code(input, errors="strict"):
1919
return input.replace(pair[0], pair[1]), len(input)
2020
return code
2121
add("replace", replace, replace, r"^replace[-_]?((?!.*(.).*\2)..)$", guess=None)
@@ -26,7 +26,7 @@ def code(input, error="strict"):
2626

2727

2828
def substitute(token, replacement):
29-
def code(input, error="strict"):
29+
def code(input, errors="strict"):
3030
return input.replace(token, replacement), len(input)
3131
return code
3232
add("substitute", substitute, substitute, r"^substitute[-_]?(.*?)/(.*?)$", guess=None)

docs/manipulations.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ These transformation functions are simple string transformations, including `str
1616
`pascalcase` | text --> pascal-case text | `pascal` | no decoding
1717
`slugify` | text --> slug | `slug`, `kebab`, `kebabcase` | no decoding
1818
`snakecase` | text --> snake-case text | `snake` | no decoding
19-
`swapcase` | text <-> case-swapped text | `swap` |
19+
`swapcase` | text <-> case-swapped text | `swap`, `invert`, `invertcase` |
2020
`title` | text <-> titled text | | decoding "untitles" the text
2121
`uppercase` | text <-> uppercase text | `upper` | decoding is `lowercase`
2222

0 commit comments

Comments
 (0)