def _fix_nan(ret, alg): """Detect an NaN and replace or raise a ValueError.""" t = [] for r in ret: if isfloat(r, num_only=True) and isnan(r): if alg & _ns['NANLAST']: t.append(float('+inf')) else: t.append(float('-inf')) else: t.append(r) return tuple(t)
def _natsort_key(val, key, alg): """\ Key to sort strings and numbers naturally. It works by separating out the numbers from the strings. This function for internal use only. See the natsort_keygen documentation for details of each parameter. Parameters ---------- val : {str, unicode} key : callable alg : ns enum Returns ------- out : tuple The modified value with numbers extracted. """ # Convert the arguments to the proper input tuple try: use_locale = alg & _ns['LOCALE'] inp_options = (alg & _NUMBER_ALGORITHMS, localeconv()['decimal_point'] if use_locale else '.') except TypeError: msg = "_natsort_key: 'alg' argument must be from the enum 'ns'" raise ValueError(msg + ', got {0}'.format(py23_str(alg))) # Get the proper regex and conversion function. try: regex, num_function = _regex_and_num_function_chooser[inp_options] except KeyError: # pragma: no cover if inp_options[1] not in ('.', ','): # pragma: no cover raise ValueError("_natsort_key: currently natsort only supports " "the decimal separators '.' and ','. " "Please file a bug report.") else: raise else: # Apply key if needed. if key is not None: val = key(val) # If this is a path, convert it. # An AttrubuteError is raised if not a string. split_as_path = False if alg & _ns['PATH']: try: val = _path_splitter(val) except AttributeError: pass else: # Record that this string was split as a path so that # we don't set PATH in the recursive call. split_as_path = True # Assume the input are strings, which is the most common case. # Apply the string modification if needed. orig_val = val try: lowfirst = alg & _ns['LOWERCASEFIRST'] dumb = dumb_sort() if use_locale else False if use_locale and dumb and not lowfirst: # pragma: no cover val = val.swapcase() # Compensate for bad locale lib. elif lowfirst and not (use_locale and dumb): val = val.swapcase() if alg & _ns['IGNORECASE']: val = val.casefold() if PY_VERSION >= 3.3 else val.lower() gl = alg & _ns['GROUPLETTERS'] ret = tuple( _number_extracter(val, regex, num_function, alg & _ns['TYPESAFE'], use_locale, gl or (use_locale and dumb))) # Handle NaN. if any(isfloat(x, num_only=True) and isnan(x) for x in ret): ret = _fix_nan(ret, alg) # For UNGROUPLETTERS, so the high level grouping can occur # based on the first letter of the string. # Do no locale transformation of the characters. if use_locale and alg & _ns['UNGROUPLETTERS']: if not ret: return (ret, ret) elif ret[0] == null_string: return ((b'' if use_pyicu else '', ), ret) elif dumb: # pragma: no cover if lowfirst: return ((orig_val[0].swapcase(), ), ret) else: return ((orig_val[0], ), ret) else: return ((val[0], ), ret) else: return ret except (TypeError, AttributeError): # Check if it is a bytes type, and if so return as a # one element tuple. if type(val) in (bytes, ): return (val.lower(), ) if alg & _ns['IGNORECASE'] else (val, ) # If not strings, assume it is an iterable that must # be parsed recursively. Do not apply the key recursively. # If this string was split as a path, turn off 'PATH'. try: was_path = alg & _ns['PATH'] newalg = alg & _ALL_BUT_PATH newalg |= (was_path * (not split_as_path)) return tuple([_natsort_key(x, None, newalg) for x in val]) # If there is still an error, it must be a number. # Return as-is, with a leading empty string. except TypeError: n = null_string if use_locale else '' if isfloat(val, num_only=True) and isnan(val): val = _fix_nan([val], alg)[0] return (( n, val, ), ) if alg & _ns['PATH'] else ( n, val, )
def _natsort_key(val, key, alg): """\ Key to sort strings and numbers naturally. It works by separating out the numbers from the strings. This function for internal use only. See the natsort_keygen documentation for details of each parameter. Parameters ---------- val : {str, unicode} key : callable alg : ns enum Returns ------- out : tuple The modified value with numbers extracted. """ # Convert the arguments to the proper input tuple try: use_locale = alg & _ns['LOCALE'] inp_options = (alg & _NUMBER_ALGORITHMS, localeconv()['decimal_point'] if use_locale else '.') except TypeError: msg = "_natsort_key: 'alg' argument must be from the enum 'ns'" raise ValueError(msg+', got {0}'.format(py23_str(alg))) # Get the proper regex and conversion function. try: regex, num_function = _regex_and_num_function_chooser[inp_options] except KeyError: # pragma: no cover if inp_options[1] not in ('.', ','): # pragma: no cover raise ValueError("_natsort_key: currently natsort only supports " "the decimal separators '.' and ','. " "Please file a bug report.") else: raise else: # Apply key if needed. if key is not None: val = key(val) # If this is a path, convert it. # An AttrubuteError is raised if not a string. split_as_path = False if alg & _ns['PATH']: try: val = _path_splitter(val) except AttributeError: pass else: # Record that this string was split as a path so that # we don't set PATH in the recursive call. split_as_path = True # Assume the input are strings, which is the most common case. # Apply the string modification if needed. orig_val = val try: lowfirst = alg & _ns['LOWERCASEFIRST'] dumb = dumb_sort() if use_locale else False if use_locale and dumb and not lowfirst: # pragma: no cover val = val.swapcase() # Compensate for bad locale lib. elif lowfirst and not (use_locale and dumb): val = val.swapcase() if alg & _ns['IGNORECASE']: val = val.casefold() if PY_VERSION >= 3.3 else val.lower() gl = alg & _ns['GROUPLETTERS'] ret = tuple(_number_extracter(val, regex, num_function, alg & _ns['TYPESAFE'], use_locale, gl or (use_locale and dumb))) # Handle NaN. if any(isfloat(x, num_only=True) and isnan(x) for x in ret): ret = _fix_nan(ret, alg) # For UNGROUPLETTERS, so the high level grouping can occur # based on the first letter of the string. # Do no locale transformation of the characters. if use_locale and alg & _ns['UNGROUPLETTERS']: if not ret: return (ret, ret) elif ret[0] == null_string: return ((b'' if use_pyicu else '',), ret) elif dumb: # pragma: no cover if lowfirst: return ((orig_val[0].swapcase(),), ret) else: return ((orig_val[0],), ret) else: return ((val[0],), ret) else: return ret except (TypeError, AttributeError): # Check if it is a bytes type, and if so return as a # one element tuple. if type(val) in (bytes,): return (val.lower(),) if alg & _ns['IGNORECASE'] else (val,) # If not strings, assume it is an iterable that must # be parsed recursively. Do not apply the key recursively. # If this string was split as a path, turn off 'PATH'. try: was_path = alg & _ns['PATH'] newalg = alg & _ALL_BUT_PATH newalg |= (was_path * (not split_as_path)) return tuple([_natsort_key(x, None, newalg) for x in val]) # If there is still an error, it must be a number. # Return as-is, with a leading empty string. except TypeError: n = null_string if use_locale else '' if isfloat(val, num_only=True) and isnan(val): val = _fix_nan([val], alg)[0] return ((n, val,),) if alg & _ns['PATH'] else (n, val,)