pax_global_header00006660000000000000000000000064125430170760014517gustar00rootroot0000000000000052 comment=03cc17b9f5c9e6ff30f5c12ed276e672c91b0371 natsort-4.0.3/000077500000000000000000000000001254301707600132155ustar00rootroot00000000000000natsort-4.0.3/.coveragerc000066400000000000000000000007701254301707600153420ustar00rootroot00000000000000[report] # Regexes for lines to exclude from consideration exclude_lines = # Have to re-enable the standard pragma pragma: no cover # Don't complain if tests don't hit defensive assertion code: raise AssertionError raise NotImplementedError raise$ # Don't complain if non-runnable code isn't run: if 0: if __name__ == .__main__.: ignore_errors = True # Files to not perform coverage on omit = natsort/__init__.* natsort/py23compat.* natsort/_version.* natsort-4.0.3/.gitignore000066400000000000000000000004671254301707600152140ustar00rootroot00000000000000*.py[co] # Packages *.egg *.eggs *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg # We are using MANIFEST.in instead MANIFEST # Installer logs pip-log.txt # Unit test / coverage reports .hypothesis .coverage .tox .cache .pytest #Translations *.mo #Mr Developer .mr.developer.cfg natsort-4.0.3/.hgignore000066400000000000000000000005051254301707600150200ustar00rootroot00000000000000syntax: glob *.py[co] # Packages *.egg *.eggs *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg # We are using MANIFEST.in instead MANIFEST # Installer logs pip-log.txt # Unit test / coverage reports .hypothesis .coverage .tox .cache .pytest #Translations *.mo #Mr Developer .mr.developer.cfg natsort-4.0.3/.travis.yml000066400000000000000000000017151254301707600153320ustar00rootroot00000000000000language: python python: - 2.6 - 2.7 - 3.2 - 3.3 - 3.4 env: - WITH_OPTIONS=true - WITH_OPTIONS=false before_install: - sudo apt-get update - sudo locale-gen de_DE.UTF-8 - sudo apt-get install bc install: - pip install -U pip - if [[ $WITH_OPTIONS == true ]]; then sudo apt-get install libicu-dev; fi - if [[ $WITH_OPTIONS == true ]]; then pip install fastnumbers; fi - if [[ $WITH_OPTIONS == true ]]; then pip install PyICU; fi - if [[ 1 -eq $(echo "$TRAVIS_PYTHON_VERSION < 3.4" | bc -l) ]]; then pip install pathlib; fi - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install argparse; fi - if [[ $(echo "$TRAVIS_PYTHON_VERSION < 3.3" | bc -l) ]]; then pip install mock; fi - pip install pytest-cov pytest-flakes pytest-pep8 hypothesis - pip install coveralls script: - python -m pytest --cov natsort --flakes --pep8 - python -m pytest --doctest-modules natsort - python -m pytest README.rst docs/source/intro.rst docs/source/examples.rst after_success: coveralls natsort-4.0.3/LICENSE000066400000000000000000000020471254301707600142250ustar00rootroot00000000000000Copyright (c) 2012-2015 Seth M. Morton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. natsort-4.0.3/MANIFEST.in000066400000000000000000000021211254301707600147470ustar00rootroot00000000000000include README.rst include LICENSE include natsort/natsort.py include natsort/_version.py include natsort/__main__.py include natsort/__init__.py include natsort/locale_help.py include natsort/utils.py include natsort/ns_enum.py include natsort/unicode_numbers.py include natsort/compat/__init__.py include natsort/compat/py23.py include natsort/compat/fake_fastnumbers.py include natsort/compat/fastnumbers.py include natsort/compat/locale.py include natsort/compat/pathlib.py include natsort/compat/pathlib.py include test_natsort/profile_natsorted.py include test_natsort/stress_natsort.py include test_natsort/slow_splitters.py include test_natsort/test_natsort.py include test_natsort/test_locale_help.py include test_natsort/test_fake_fastnumbers.py include test_natsort/test_main.py include test_natsort/test_utils.py include test_natsort/test_unicode_numbers.py include test_natsort/compat/__init__.py include test_natsort/compat/hypothesis.py include test_natsort/compat/locale.py include test_natsort/compat/mock.py include setup.py include setup.cfg prune natsort/__pycache__ graft docs/source natsort-4.0.3/README.rst000066400000000000000000000217701254301707600147130ustar00rootroot00000000000000natsort ======= .. image:: https://travis-ci.org/SethMMorton/natsort.svg?branch=master :target: https://travis-ci.org/SethMMorton/natsort .. image:: https://coveralls.io/repos/SethMMorton/natsort/badge.png?branch=master :target: https://coveralls.io/r/SethMMorton/natsort?branch=master Natural sorting for python. - Source Code: https://github.com/SethMMorton/natsort - Downloads: https://pypi.python.org/pypi/natsort - Documentation: http://pythonhosted.org/natsort Please see `Moving from older Natsort versions`_ to see if this update requires you to modify your ``natsort`` calls in your code (99% of users will not). Quick Description ----------------- When you try to sort a list of strings that contain numbers, the normal python sort algorithm sorts lexicographically, so you might not get the results that you expect: .. code-block:: python >>> a = ['a2', 'a9', 'a1', 'a4', 'a10'] >>> sorted(a) ['a1', 'a10', 'a2', 'a4', 'a9'] Notice that it has the order ('1', '10', '2') - this is because the list is being sorted in lexicographical order, which sorts numbers like you would letters (i.e. 'b', 'ba', 'c'). ``natsort`` provides a function ``natsorted`` that helps sort lists "naturally", either as real numbers (i.e. signed/unsigned floats or ints), or as versions. Using ``natsorted`` is simple: .. code-block:: python >>> from natsort import natsorted >>> a = ['a2', 'a9', 'a1', 'a4', 'a10'] >>> natsorted(a) ['a1', 'a2', 'a4', 'a9', 'a10'] ``natsorted`` identifies real numbers anywhere in a string and sorts them naturally. Sorting versions is handled properly by default (as of ``natsort`` version >= 4.0.0): .. code-block:: python >>> a = ['version-1.9', 'version-2.0', 'version-1.11', 'version-1.10'] >>> natsorted(a) ['version-1.9', 'version-1.10', 'version-1.11', 'version-2.0'] If you need to sort release candidates, please see `this useful hack `_ . You can also perform locale-aware sorting (or "human sorting"), where the non-numeric characters are ordered based on their meaning, not on their ordinal value; this can be achieved with the ``humansorted`` function: .. code-block:: python >>> a = ['Apple', 'Banana', 'apple', 'banana'] >>> natsorted(a) ['Apple', 'Banana', 'apple', 'banana'] >>> import locale >>> locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') 'en_US.UTF-8' >>> from natsort import humansorted >>> humansorted(a) ['apple', 'Apple', 'banana', 'Banana'] You may find you need to explicitly set the locale to get this to work (as shown in the example). Please see the `following caveat `_ and the `Optional Dependencies`_ section below before using the ``humansorted`` function, *especially* if you are on a BSD-based system (like Mac OS X). You can sort signed floats (i.e. real numbers) using the ``realsorted``; this is useful in scientific data analysis. This was the default behavior of ``natsorted`` for ``natsort`` version < 4.0.0: .. code-block:: python >>> from natsort import realsorted >>> a = ['num5.10', 'num-3', 'num5.3', 'num2'] >>> natsorted(a) ['num2', 'num5.3', 'num5.10', 'num-3'] >>> realsorted(a) ['num-3', 'num2', 'num5.10', 'num5.3'] You can mix and match ``int``, ``float``, and ``str`` (or ``unicode``) types when you sort: .. code-block:: python >>> a = ['4.5', 6, 2.0, '5', 'a'] >>> natsorted(a) [2.0, '4.5', '5', 6, 'a'] >>> # On Python 2, sorted(a) would return [2.0, 6, '4.5', '5', 'a'] >>> # On Python 3, sorted(a) would raise an "unorderable types" TypeError ``natsort`` does not officially support the ``bytes`` type on Python 3, but convenience functions are provided that help you decode to ``str`` first: .. code-block:: python >>> from natsort import as_utf8 >>> a = [b'a', 14.0, 'b'] >>> # On Python 2, natsorted(a) would would work as expected. >>> # On Python 3, natsorted(a) would raise a TypeError (bytes() < str()) >>> natsorted(a, key=as_utf8) == [14.0, b'a', 'b'] True >>> a = [b'a56', b'a5', b'a6', b'a40'] >>> # On Python 2, natsorted(a) would would work as expected. >>> # On Python 3, natsorted(a) would return the same results as sorted(a) >>> natsorted(a, key=as_utf8) == [b'a5', b'a6', b'a40', b'a56'] True The natsort algorithm does other fancy things like - recursively descend into lists of lists - control the case-sensitivity - sort file paths correctly - allow custom sorting keys - exposes a natsort_key generator to pass to ``list.sort`` Please see the package documentation for more details, including `examples and recipes `_. Shell script ------------ ``natsort`` comes with a shell script called ``natsort``, or can also be called from the command line with ``python -m natsort``. Requirements ------------ ``natsort`` requires Python version 2.7 or greater or Python 3.2 or greater. .. _optional: Optional Dependencies --------------------- fastnumbers ''''''''''' The most efficient sorting can occur if you install the `fastnumbers `_ package (it helps with the string to number conversions.) ``natsort`` will still run (efficiently) without the package, but if you need to squeeze out that extra juice it is recommended you include this as a dependency. ``natsort`` will not require (or check) that `fastnumbers `_ is installed at installation. PyICU ''''' On BSD-based systems (this includes Mac OS X), the underlying ``locale`` library can be buggy (please see http://bugs.python.org/issue23195); ``locale`` is used for the ``ns.LOCALE`` option and ``humansorted`` function.. To remedy this, one can 1. Use "\*.ISO8859-1" locale (i.e. 'en_US.ISO8859-1') rather than "\*.UTF-8" locale. These locales do not suffer from as many problems as "UTF-8" and thus should give expected results. 2. Use `PyICU `_. If `PyICU `_ is installed, ``natsort`` will use it under the hood; this will give more reliable cross-platform results in the long run. ``natsort`` will not require (or check) that `PyICU `_ is installed at installation. Please visit https://github.com/SethMMorton/natsort/issues/21 for more details and how to install on Mac OS X. **Please note** that using `PyICU `_ is the only way to guarantee correct results for all input on BSD-based systems, since every other suggestion is a workaround. 3. Do nothing. As of ``natsort`` version 4.0.0, ``natsort`` is configured to compensate for a broken ``locale`` library in terms of case-handling; if you do not need to be able to properly handle non-ASCII characters then this may be the best option for you. Note that the above solutions *should not* be required for Windows or Linux since in Linux-based systems and Windows systems ``locale`` *should* work just fine. .. _deprecate: Moving from older Natsort versions ---------------------------------- - The default sorting algorithm for ``natsort`` has changed in version 4.0.0 from signed floats (with exponents) to unsigned integers. The motivation for this change is that it will cause ``natsort`` to return results that pass the "least astonishment" test for the most common use case, which is sorting version numbers. If you relied on the default behavior to be signed floats, add ``alg=ns.F | ns.S`` to your ``natsort`` calls or switch to the new ``realsorted`` function which behaves identically to the older ``natsorted`` with default values. For 99% of users this change will not effect their code... it is only expected that this will effect users using ``natsort`` for science and engineering. This will also affect the default behavior of the ``natsort`` shell script. - In ``natsort`` version 4.0.0, the ``number_type``, ``signed``, ``exp``, ``as_path``, and ``py3_safe`` options have be removed from the (documented) API in favor of the ``alg`` option and ``ns`` enum. - In ``natsort`` version 4.0.0, the ``natsort_key`` function has been removed from the public API. Author ------ Seth M. Morton History ------- These are the last three entries of the changelog. See the package documentation for the complete `changelog `_. 06-25-2015 v. 4.0.3 ''''''''''''''''''' - Fixed bad install on last release (sorry guys!). 06-24-2015 v. 4.0.2 ''''''''''''''''''' - Added back Python 2.6 and Python 3.2 compatibility. Unit testing is now performed for these versions. - Consolidated under-the-hood compatibility functionality. 06-04-2015 v. 4.0.1 ''''''''''''''''''' - Added support for sorting NaN by internally converting to -Infinity or +Infinity natsort-4.0.3/docs/000077500000000000000000000000001254301707600141455ustar00rootroot00000000000000natsort-4.0.3/docs/source/000077500000000000000000000000001254301707600154455ustar00rootroot00000000000000natsort-4.0.3/docs/source/api.rst000066400000000000000000000005431254301707600167520ustar00rootroot00000000000000.. default-domain:: py .. currentmodule:: natsort .. _api: natsort API =========== .. toctree:: :maxdepth: 2 natsort_keygen.rst natsorted.rst versorted.rst humansorted.rst realsorted.rst index_natsorted.rst index_versorted.rst index_humansorted.rst index_realsorted.rst order_by_index.rst ns_class.rst bytes.rst natsort-4.0.3/docs/source/bytes.rst000066400000000000000000000010551254301707600173260ustar00rootroot00000000000000.. default-domain:: py .. currentmodule:: natsort .. _bytes_help: Help With Bytes On Python 3 =========================== The official stance of :mod:`natsort` is to not support `bytes` for sorting; there is just too much that can go wrong when trying to automate conversion between `bytes` and `str`. But rather than completely give up on `bytes`, :mod:`natsort` provides three functions that make it easy to quickly decode `bytes` to `str` so that sorting is possible. .. autofunction:: decoder .. autofunction:: as_ascii .. autofunction:: as_utf8 natsort-4.0.3/docs/source/changelog.rst000066400000000000000000000216601254301707600201330ustar00rootroot00000000000000.. _changelog: Changelog --------- 06-25-2015 v. 4.0.3 ''''''''''''''''''' - Fixed bad install on last release (sorry guys!). 06-24-2015 v. 4.0.2 ''''''''''''''''''' - Added back Python 2.6 and Python 3.2 compatibility. Unit testing is now performed for these versions. - Consolidated under-the-hood compatibility functionality. 06-04-2015 v. 4.0.1 ''''''''''''''''''' - Added support for sorting NaN by internally converting to -Infinity or +Infinity 05-17-2015 v. 4.0.0 ''''''''''''''''''' - Made default behavior of 'natsort' search for unsigned ints, rather than signed floats. This is a backwards-incompatible change but in 99% of use cases it should not require any end-user changes. - Improved handling of locale-aware sorting on systems where the underlying locale library is broken. - Greatly improved all unit tests by adding the hypothesis library. 04-06-2015 v. 3.5.6 ''''''''''''''''''' - Added 'UNGROUPLETTERS' algorithm to get the case-grouping behavior of an ordinal sort when using 'LOCALE'. - Added convenience functions 'decoder', 'as_ascii', and 'as_utf8' for dealing with bytes types. 04-04-2015 v. 3.5.5 ''''''''''''''''''' - Added 'realsorted' and 'index_realsorted' functions for forward-compatibility with >= 4.0.0. - Made explanation of when to use "TYPESAFE" more clear in the docs. 04-02-2015 v. 3.5.4 ''''''''''''''''''' - Fixed bug where a 'TypeError' was raised if a string containing a leading number was sorted with alpha-only strings when 'LOCALE' is used. 03-26-2015 v. 3.5.3 ''''''''''''''''''' - Fixed bug where '--reverse-filter' option in shell script was not getting checked for correctness. - Documentation updates to better describe locale bug, and illustrate upcoming default behavior change. - Internal improvements, including making test suite more granular. 01-13-2015 v. 3.5.2 ''''''''''''''''''' - Enhancement that will convert a 'pathlib.Path' object to a 'str' if 'ns.PATH' is enabled. 09-25-2014 v. 3.5.1 ''''''''''''''''''' - Fixed bug that caused list/tuples to fail when using 'ns.LOWECASEFIRST' or 'ns.IGNORECASE'. - Refactored modules so that only the public API was in natsort.py and ns_enum.py. - Refactored all import statements to be absolute, not relative. 09-02-2014 v. 3.5.0 ''''''''''''''''''' - Added the 'alg' argument to the 'natsort' functions. This argument accepts an enum that is used to indicate the options the user wishes to use. The 'number_type', 'signed', 'exp', 'as_path', and 'py3_safe' options are being deprecated and will become (undocumented) keyword-only options in natsort version 4.0.0. - The user can now modify how 'natsort' handles the case of non-numeric characters. - The user can now instruct 'natsort' to use locale-aware sorting, which allows 'natsort' to perform true "human sorting". - The `humansorted` convenience function has been included to make this easier. - Updated shell script with locale functionality. 08-12-2014 v. 3.4.1 ''''''''''''''''''' - 'natsort' will now use the 'fastnumbers' module if it is installed. This gives up to an extra 30% boost in speed over the previous performance enhancements. - Made documentation point to more 'natsort' resources, and also added a new example in the examples section. 07-19-2014 v. 3.4.0 ''''''''''''''''''' - Fixed a bug that caused user's options to the 'natsort_key' to not be passed on to recursive calls of 'natsort_key'. - Added a 'natsort_keygen' function that will generate a wrapped version of 'natsort_key' that is easier to call. 'natsort_key' is now set to deprecate at natsort version 4.0.0. - Added an 'as_path' option to 'natsorted' & co. that will try to treat input strings as filepaths. This will help yield correct results for OS-generated inputs like ``['/p/q/o.x', '/p/q (1)/o.x', '/p/q (10)/o.x', '/p/q/o (1).x']``. - Massive performance enhancements for string input (1.8x-2.0x), at the expense of reduction in speed for numeric input (~2.0x). - This is a good compromise because the most common input will be strings, not numbers, and sorting numbers still only takes 0.6x the time of sorting strings. If you are sorting only numbers, you would use 'sorted' anyway. - Added the 'order_by_index' function to help in using the output of 'index_natsorted' and 'index_versorted'. - Added the 'reverse' option to 'natsorted' & co. to make it's API more similar to the builtin 'sorted'. - Added more unit tests. - Added auxillary test code that helps in profiling and stress-testing. - Reworked the documentation, moving most of it to PyPI's hosting platform. - Added support for coveralls.io. - Entire codebase is now PyFlakes and PEP8 compliant. 06-28-2014 v. 3.3.0 ''''''''''''''''''' - Added a 'versorted' method for more convenient sorting of versions. - Updated command-line tool --number_type option with 'version' and 'ver' to make it more clear how to sort version numbers. - Moved unit-testing mechanism from being docstring-based to actual unit tests in actual functions. - This has provided the ability determine the coverage of the unit tests (99%). - This also makes the pydoc documentation a bit more clear. - Made docstrings for public functions mirror the README API. - Connected natsort development to Travis-CI to help ensure quality releases. 06-20-2014 v. 3.2.1 ''''''''''''''''''' - Re-"Fixed" unorderable types issue on Python 3.x - this workaround is for when the problem occurs in the middle of the string. 05-07-2014 v. 3.2.0 ''''''''''''''''''' - "Fixed" unorderable types issue on Python 3.x with a workaround that attempts to replicate the Python 2.x behavior by putting all the numbers (or strings that begin with numbers) first. - Now explicitly excluding __pycache__ from releases by adding a prune statement to MANIFEST.in. 05-05-2014 v. 3.1.2 ''''''''''''''''''' - Added setup.cfg to support universal wheels. - Added Python 3.0 and Python 3.1 as requiring the argparse module. 03-01-2014 v. 3.1.1 ''''''''''''''''''' - Added ability to sort lists of lists. - Cleaned up import statements. 01-20-2014 v. 3.1.0 ''''''''''''''''''' - Added the ``signed`` and ``exp`` options to allow finer tuning of the sorting - Entire codebase now works for both Python 2 and Python 3 without needing to run ``2to3``. - Updated all doctests. - Further simplified the ``natsort`` base code by removing unneeded functions. - Simplified documentation where possible. - Improved the shell script code - Made the documentation less "path"-centric to make it clear it is not just for sorting file paths. - Removed the filesystem-based options because these can be achieved better though a pipeline. - Added doctests. - Added new options that correspond to ``signed`` and ``exp``. - The user can now specify multiple numbers to exclude or multiple ranges to filter by. 10-01-2013 v. 3.0.2 ''''''''''''''''''' - Made float, int, and digit searching algorithms all share the same base function. - Fixed some outdated comments. - Made the ``__version__`` variable available when importing the module. 8-15-2013 v. 3.0.1 '''''''''''''''''' - Added support for unicode strings. - Removed extraneous ``string2int`` function. - Fixed empty string removal function. 7-13-2013 v. 3.0.0 '''''''''''''''''' - Added a ``number_type`` argument to the sorting functions to specify how liberal to be when deciding what a number is. - Reworked the documentation. 6-25-2013 v. 2.2.0 '''''''''''''''''' - Added ``key`` attribute to ``natsorted`` and ``index_natsorted`` so that it mimics the functionality of the built-in ``sorted`` - Added tests to reflect the new functionality, as well as tests demonstrating how to get similar functionality using ``natsort_key``. 12-5-2012 v. 2.1.0 '''''''''''''''''' - Reorganized package. - Now using a platform independent shell script generator (entry_points from distribute). - Can now execute natsort from command line with ``python -m natsort`` as well. 11-30-2012 v. 2.0.2 ''''''''''''''''''' - Added the use_2to3 option to setup.py. - Added distribute_setup.py to the distribution. - Added dependency to the argparse module (for python2.6). 11-21-2012 v. 2.0.1 ''''''''''''''''''' - Reorganized directory structure. - Added tests into the natsort.py file iteself. 11-16-2012, v. 2.0.0 '''''''''''''''''''' - Updated sorting algorithm to support floats (including exponentials) and basic version number support. - Added better README documentation. - Added doctests. natsort-4.0.3/docs/source/conf.py000066400000000000000000000214101254301707600167420ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # natsort documentation build configuration file, created by # sphinx-quickstart on Thu Jul 17 21:01:29 2014. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os import re def current_version(): # Read the _version.py file for the module version number VERSIONFILE = os.path.join('..', '..', 'natsort', '_version.py') versionsearch = re.compile(r"^__version__ = ['\"]([^'\"]*)['\"]") with open(VERSIONFILE, "rt") as fl: for line in fl: m = versionsearch.search(line) if m: return m.group(1) else: s = "Unable to locate version string in {0}" raise RuntimeError(s.format(VERSIONFILE)) # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.intersphinx', 'numpydoc', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'natsort' copyright = u'2014, Seth M. Morton' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The full version, including alpha/beta/rc tags. release = current_version() # The short X.Y version. version = '.'.join(release.split('.')[0:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['solar/*'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' highlight_language = 'python' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'solar' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ['.'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'natsortdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'natsort.tex', u'natsort Documentation', u'Seth M. Morton', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'natsort', u'natsort Documentation', [u'Seth M. Morton'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'natsort', u'natsort Documentation', u'Seth M. Morton', 'natsort', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} natsort-4.0.3/docs/source/examples.rst000066400000000000000000000320361254301707600200210ustar00rootroot00000000000000.. default-domain:: py .. currentmodule:: natsort .. _examples: Examples and Recipes ==================== If you want more detailed examples than given on this page, please see https://github.com/SethMMorton/natsort/tree/master/test_natsort. Basic Usage ----------- In the most basic use case, simply import :func:`~natsorted` and use it as you would :func:`sorted`:: >>> a = ['a2', 'a9', 'a1', 'a4', 'a10'] >>> sorted(a) ['a1', 'a10', 'a2', 'a4', 'a9'] >>> from natsort import natsorted, ns >>> natsorted(a) ['a1', 'a2', 'a4', 'a9', 'a10'] Sort Version Numbers -------------------- As of :mod:`natsort` version >= 4.0.0, :func:`~natsorted` will now properly sort version numbers. The old function :func:`~versorted` exists for backwards compatibility but new development should use :func:`~natsorted`. .. _rc_sorting: Sorting with Alpha, Beta, and Release Candidates ++++++++++++++++++++++++++++++++++++++++++++++++ By default, if you wish to sort versions with a non-strict versioning scheme, you may not get the results you expect:: >>> a = ['1.2', '1.2rc1', '1.2beta2', '1.2beta1', '1.2alpha', '1.2.1', '1.1', '1.3'] >>> natsorted(a) ['1.1', '1.2', '1.2.1', '1.2alpha', '1.2beta1', '1.2beta2', '1.2rc1', '1.3'] To make the '1.2' pre-releases come before '1.2.1', you need to use the following recipe:: >>> natsorted(a, key=lambda x: x.replace('.', '~')) ['1.1', '1.2', '1.2alpha', '1.2beta1', '1.2beta2', '1.2rc1', '1.2.1', '1.3'] If you also want '1.2' after all the alpha, beta, and rc candidates, you can modify the above recipe:: >>> natsorted(a, key=lambda x: x.replace('.', '~')+'z') ['1.1', '1.2alpha', '1.2beta1', '1.2beta2', '1.2rc1', '1.2', '1.2.1', '1.3'] Please see `this issue `_ to see why this works. Sort OS-Generated Paths ----------------------- In some cases when sorting file paths with OS-Generated names, the default :mod:`~natsorted` algorithm may not be sufficient. In cases like these, you may need to use the ``ns.PATH`` option:: >>> a = ['./folder/file (1).txt', ... './folder/file.txt', ... './folder (1)/file.txt', ... './folder (10)/file.txt'] >>> natsorted(a) ['./folder (1)/file.txt', './folder (10)/file.txt', './folder/file (1).txt', './folder/file.txt'] >>> natsorted(a, alg=ns.PATH) ['./folder/file.txt', './folder/file (1).txt', './folder (1)/file.txt', './folder (10)/file.txt'] Locale-Aware Sorting (Human Sorting) ------------------------------------ You can instruct :mod:`natsort` to use locale-aware sorting with the ``ns.LOCALE`` option. In addition to making this understand non-ASCII characters, it will also properly interpret non-'.' decimal separators and also properly order case. It may be more convenient to just use the :func:`humansorted` function:: >>> from natsort import humansorted >>> import locale >>> locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') 'en_US.UTF-8' >>> a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana'] >>> natsorted(a, alg=ns.LOCALE) ['apple', 'Apple', 'banana', 'Banana', 'corn', 'Corn'] >>> humansorted(a) ['apple', 'Apple', 'banana', 'Banana', 'corn', 'Corn'] You may find that if you do not explicitly set the locale your results may not be as you expect... I have found that it depends on the system you are on. If you use `PyICU `_ (see below) then you should not need to do this. .. _bug_note: Known Bugs When Using Locale-Aware Sorting On BSD-Based OSs (Including Mac OS X) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ If you find that ``ns.LOCALE`` (or :func:`~humansorted`) does not give the results you expect, before filing a bug report please try to first install `PyICU `_; this *especially* applies to users on BSD-based systems (like Mac OS X). There are some known bugs with the ``locale`` module from the standard library that are solved when using `PyICU `_; you can read about them here: http://bugs.python.org/issue23195. If you have problems with ``ns.LOCALE`` (or :func:`~humansorted`), especially on BSD-based systems, you can try the following: 1. Use "\*.ISO8859-1" locale (i.e. 'en_US.ISO8859-1') rather than "\*.UTF-8" locale. These locales do not suffer from as many problems as "UTF-8" and thus should give expected results. 2. Use `PyICU `_. If `PyICU `_ is installed, ``natsort`` will use it under the hood; this will give more reliable cross-platform results in the long run. ``natsort`` will not require (or check) that `PyICU `_ is installed at installation. Please visit https://github.com/SethMMorton/natsort/issues/21 for more details and how to install on Mac OS X. **Please note** that using `PyICU `_ is the only way to guarantee correct results for all input on BSD-based systems, since every other suggestion is a workaround. 3. Do nothing. As of ``natsort`` version 4.0.0, ``natsort`` is configured to compensate for a broken ``locale`` library in terms of case-handling; if you do not need to be able to properly handle non-ASCII characters then this may be the best option for you. Note that the above solutions *should not* be required for Windows or Linux since in Linux-based systems and Windows systems ``locale`` *should* work just fine. Controlling Case When Sorting ----------------------------- For non-numbers, by default :mod:`natsort` used ordinal sorting (i.e. it sorts by the character's value in the ASCII table). For example:: >>> a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana'] >>> natsorted(a) ['Apple', 'Banana', 'Corn', 'apple', 'banana', 'corn'] There are times when you wish to ignore the case when sorting, you can easily do this with the ``ns.IGNORECASE`` option:: >>> natsorted(a, alg=ns.IGNORECASE) ['Apple', 'apple', 'Banana', 'banana', 'corn', 'Corn'] Note thats since Python's sorting is stable, the order of equivalent elements after lowering the case is the same order they appear in the original list. Upper-case letters appear first in the ASCII table, but many natural sorting methods place lower-case first. To do this, use ``ns.LOWERCASEFIRST``:: >>> natsorted(a, alg=ns.LOWERCASEFIRST) ['apple', 'banana', 'corn', 'Apple', 'Banana', 'Corn'] It may be undesirable to have the upper-case letters grouped together and the lower-case letters grouped together; most would expect all "a"s to bet together regardless of case, and all "b"s, and so on. To achieve this, use ``ns.GROUPLETTERS``:: >>> natsorted(a, alg=ns.GROUPLETTERS) ['Apple', 'apple', 'Banana', 'banana', 'Corn', 'corn'] You might combine this with ``ns.LOWERCASEFIRST`` to get what most would expect to be "natural" sorting:: >>> natsorted(a, alg=ns.G | ns.LF) ['apple', 'Apple', 'banana', 'Banana', 'corn', 'Corn'] Customizing Float Definition ---------------------------- You can make :func:`~natsorted` search for any float that would be a valid Python float literal, such as 5, 0.4, -4.78, +4.2E-34, etc. using the ``ns.FLOAT`` key. You can disable the exponential component of the number with ``ns.NOEXP``. :: >>> a = ['a50', 'a51.', 'a+50.4', 'a5.034e1', 'a+50.300'] >>> natsorted(a, alg=ns.FLOAT) ['a50', 'a5.034e1', 'a51.', 'a+50.300', 'a+50.4'] >>> natsorted(a, alg=ns.FLOAT | ns.SIGNED) ['a50', 'a+50.300', 'a5.034e1', 'a+50.4', 'a51.'] >>> natsorted(a, alg=ns.FLOAT | ns.SIGNED | ns.NOEXP) ['a5.034e1', 'a50', 'a+50.300', 'a+50.4', 'a51.'] For convenience, the ``ns.REAL`` option is provided which is a shortcut for ``ns.FLOAT | ns.SIGNED`` and can be used to sort on real numbers. This can be easily accessed with the :func:`~realsorted` convenience function. Please note that the behavior of the :func:`~realsorted` function was the default behavior of :func:`~natsorted` for :mod:`natsort` version < 4.0.0:: >>> natsorted(a, alg=ns.REAL) ['a50', 'a+50.300', 'a5.034e1', 'a+50.4', 'a51.'] >>> from natsort import realsorted >>> realsorted(a) ['a50', 'a+50.300', 'a5.034e1', 'a+50.4', 'a51.'] Using a Custom Sorting Key -------------------------- Like the built-in ``sorted`` function, ``natsorted`` can accept a custom sort key so that:: >>> from operator import attrgetter, itemgetter >>> a = [['a', 'num4'], ['b', 'num8'], ['c', 'num2']] >>> natsorted(a, key=itemgetter(1)) [['c', 'num2'], ['a', 'num4'], ['b', 'num8']] >>> class Foo: ... def __init__(self, bar): ... self.bar = bar ... def __repr__(self): ... return "Foo('{0}')".format(self.bar) >>> b = [Foo('num3'), Foo('num5'), Foo('num2')] >>> natsorted(b, key=attrgetter('bar')) [Foo('num2'), Foo('num3'), Foo('num5')] Generating a Natsort Key ------------------------ If you need to sort a list in-place, you cannot use :func:`~natsorted`; you need to pass a key to the :meth:`list.sort` method. The function :func:`~natsort_keygen` is a convenient way to generate these keys for you:: >>> from natsort import natsort_keygen >>> a = ['a50', 'a51.', 'a50.4', 'a5.034e1', 'a50.300'] >>> natsort_key = natsort_keygen(alg=ns.FLOAT) >>> a.sort(key=natsort_key) >>> a ['a50', 'a50.300', 'a5.034e1', 'a50.4', 'a51.'] :func:`~natsort_keygen` has the same API as :func:`~natsorted` (minus the `reverse` option). Sorting Multiple Lists According to a Single List ------------------------------------------------- Sometimes you have multiple lists, and you want to sort one of those lists and reorder the other lists according to how the first was sorted. To achieve this you could use the :func:`~index_natsorted` in combination with the convenience function :func:`~order_by_index`:: >>> from natsort import index_natsorted, order_by_index >>> a = ['a2', 'a9', 'a1', 'a4', 'a10'] >>> b = [4, 5, 6, 7, 8] >>> c = ['hi', 'lo', 'ah', 'do', 'up'] >>> index = index_natsorted(a) >>> order_by_index(a, index) ['a1', 'a2', 'a4', 'a9', 'a10'] >>> order_by_index(b, index) [6, 4, 7, 5, 8] >>> order_by_index(c, index) ['ah', 'hi', 'do', 'lo', 'up'] Returning Results in Reverse Order ---------------------------------- Just like the :func:`sorted` built-in function, you can supply the ``reverse`` option to return the results in reverse order:: >>> a = ['a2', 'a9', 'a1', 'a4', 'a10'] >>> natsorted(a, reverse=True) ['a10', 'a9', 'a4', 'a2', 'a1'] Sorting Bytes on Python 3 ------------------------- Python 3 is rather strict about comparing strings and bytes, and this can make it difficult to deal with collections of both. Because of the challenge of guessing which encoding should be used to decode a bytes array to a string, :mod:`natsort` does *not* try to guess and automatically convert for you; in fact, the official stance of :mod:`natsort` is to not support sorting bytes. Instead, some decoding convenience functions have been provided to you (see :ref:`bytes_help`) that allow you to provide a codec for decoding bytes through the ``key`` argument that will allow :mod:`natsort` to convert byte arrays to strings for sorting; these functions know not to raise an error if the input is not a byte array, so you can use the key on any arbitrary collection of data. :: >>> from natsort import as_ascii >>> a = [b'a', 14.0, 'b'] >>> # On Python 2, natsorted(a) would would work as expected. >>> # On Python 3, natsorted(a) would raise a TypeError (bytes() < str()) >>> natsorted(a, key=as_ascii) == [14.0, b'a', 'b'] True Additionally, regular expressions cannot be run on byte arrays, making it so that :mod:`natsort` cannot parse them for numbers. As a result, if you run :mod:`natsort` on a list of bytes, you will get results that are like Python's default sorting behavior. Of course, you can use the decoding functions to solve this:: >>> from natsort import as_utf8 >>> a = [b'a56', b'a5', b'a6', b'a40'] >>> natsorted(a) # doctest: +SKIP [b'a40', b'a5', b'a56', b'a6'] >>> natsorted(a, key=as_utf8) == [b'a5', b'a6', b'a40', b'a56'] True If you need a codec different from ASCII or UTF-8, you can use :func:`decoder` to generate a custom key:: >>> from natsort import decoder >>> a = [b'a56', b'a5', b'a6', b'a40'] >>> natsorted(a, key=decoder('latin1')) == [b'a5', b'a6', b'a40', b'a56'] True Sorting a Pandas DataFrame -------------------------- As of Pandas version 0.16.0, the sorting methods do not accept a ``key`` argument, so you cannot simply pass :func:`natsort_keygen` to a Pandas DataFrame and sort. This request has been made to the Pandas devs; see `issue 3942 `_ if you are interested. If you need to sort a Pandas DataFrame, please check out `this answer on StackOverflow `_ for ways to do this without the ``key`` argument to ``sort``. natsort-4.0.3/docs/source/humansorted.rst000066400000000000000000000002151254301707600205260ustar00rootroot00000000000000.. default-domain:: py .. currentmodule:: natsort :func:`~natsort.humansorted` ============================ .. autofunction:: humansorted natsort-4.0.3/docs/source/index.rst000066400000000000000000000007751254301707600173170ustar00rootroot00000000000000.. natsort documentation master file, created by sphinx-quickstart on Thu Jul 17 21:01:29 2014. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. natsort: Natural Sorting for Python =================================== Contents: .. toctree:: :maxdepth: 2 :numbered: intro.rst examples.rst api.rst shell.rst changelog.rst Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` natsort-4.0.3/docs/source/index_humansorted.rst000066400000000000000000000002371254301707600217210ustar00rootroot00000000000000.. default-domain:: py .. currentmodule:: natsort :func:`~natsort.index_humansorted` ================================== .. autofunction:: index_humansorted natsort-4.0.3/docs/source/index_natsorted.rst000066400000000000000000000002311254301707600213650ustar00rootroot00000000000000.. default-domain:: py .. currentmodule:: natsort :func:`~natsort.index_natsorted` ================================ .. autofunction:: index_natsorted natsort-4.0.3/docs/source/index_realsorted.rst000066400000000000000000000002341254301707600215310ustar00rootroot00000000000000.. default-domain:: py .. currentmodule:: natsort :func:`~natsort.index_realsorted` ================================= .. autofunction:: index_realsorted natsort-4.0.3/docs/source/index_versorted.rst000066400000000000000000000002311254301707600213770ustar00rootroot00000000000000.. default-domain:: py .. currentmodule:: natsort :func:`~natsort.index_versorted` ================================ .. autofunction:: index_versorted natsort-4.0.3/docs/source/intro.rst000066400000000000000000000200711254301707600173320ustar00rootroot00000000000000.. default-domain:: py .. module:: natsort The :mod:`natsort` module ========================= Natural sorting for python. - Source Code: https://github.com/SethMMorton/natsort - Downloads: https://pypi.python.org/pypi/natsort - Documentation: http://pythonhosted.org/natsort/ :mod:`natsort` was initially created for sorting scientific output filenames that contained floating point numbers in the names. There was a serious lack of algorithms out there that could perform a natural sort on `floats` but plenty for `ints`; check out `this StackOverflow question `_ and its answers and links therein, `this ActiveState forum `_, and of course `this great article on natural sorting `_ from CodingHorror.com for examples of what I mean. :mod:`natsort` was created to fill in this gap. It has since grown and can now sort version numbers (which seems to be the most common use case based on user feedback) as well as some other nice features. Quick Description ----------------- When you try to sort a list of strings that contain numbers, the normal python sort algorithm sorts lexicographically, so you might not get the results that you expect:: >>> a = ['a2', 'a9', 'a1', 'a4', 'a10'] >>> sorted(a) ['a1', 'a10', 'a2', 'a4', 'a9'] Notice that it has the order ('1', '10', '2') - this is because the list is being sorted in lexicographical order, which sorts numbers like you would letters (i.e. 'b', 'ba', 'c'). :mod:`natsort` provides a function :func:`~natsorted` that helps sort lists "naturally", either as real numbers (i.e. signed/unsigned floats or ints), or as versions. Using :func:`~natsorted` is simple:: >>> from natsort import natsorted >>> a = ['a2', 'a9', 'a1', 'a4', 'a10'] >>> natsorted(a) ['a1', 'a2', 'a4', 'a9', 'a10'] :func:`~natsorted` identifies numbers anywhere in a string and sorts them naturally. Sorting versions is handled properly by default (as of :mod:`natsort` version >= 4.0.0): .. code-block:: python >>> a = ['version-1.9', 'version-2.0', 'version-1.11', 'version-1.10'] >>> natsorted(a) ['version-1.9', 'version-1.10', 'version-1.11', 'version-2.0'] If you need to sort release candidates, please see :ref:`rc_sorting` for a useful hack. You can also perform locale-aware sorting (or "human sorting"), where the non-numeric characters are ordered based on their meaning, not on their ordinal value; this can be achieved with the :func:`~humansorted` function: .. code-block:: python >>> a = ['Apple', 'Banana', 'apple', 'banana'] >>> natsorted(a) ['Apple', 'Banana', 'apple', 'banana'] >>> import locale >>> locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') 'en_US.UTF-8' >>> from natsort import humansorted >>> humansorted(a) ['apple', 'Apple', 'banana', 'Banana'] You may find you need to explicitly set the locale to get this to work (as shown in the example). Please see :ref:`bug_note` and the Installation section below before using the :func:`~humansorted` function. You can sort signed floats (i.e. real numbers) using the :func:`~realsorted`; this is useful in scientific data analysis. This was the default behavior of :func:`~natsorted` for :mod:`natsort` version < 4.0.0: .. code-block:: python >>> from natsort import realsorted >>> a = ['num5.10', 'num-3', 'num5.3', 'num2'] >>> natsorted(a) ['num2', 'num5.3', 'num5.10', 'num-3'] >>> realsorted(a) ['num-3', 'num2', 'num5.10', 'num5.3'] You can mix and match ``int``, ``float``, and ``str`` (or ``unicode``) types when you sort:: >>> a = ['4.5', 6, 2.0, '5', 'a'] >>> natsorted(a) [2.0, '4.5', '5', 6, 'a'] >>> # On Python 2, sorted(a) would return [2.0, 6, '4.5', '5', 'a'] >>> # On Python 3, sorted(a) would raise an "unorderable types" TypeError :mod:`natsort` does not officially support the `bytes` type on Python 3, but convenience functions are provided that help you decode to `str` first:: >>> from natsort import as_utf8 >>> a = [b'a', 14.0, 'b'] >>> # On Python 2, natsorted(a) would would work as expected. >>> # On Python 3, natsorted(a) would raise a TypeError (bytes() < str()) >>> natsorted(a, key=as_utf8) == [14.0, b'a', 'b'] True >>> a = [b'a56', b'a5', b'a6', b'a40'] >>> # On Python 2, natsorted(a) would would work as expected. >>> # On Python 3, natsorted(a) would return the same results as sorted(a) >>> natsorted(a, key=as_utf8) == [b'a5', b'a6', b'a40', b'a56'] True The natsort algorithm does other fancy things like - recursively descend into lists of lists - control the case-sensitivity - sort file paths correctly - allow custom sorting keys - exposes a natsort_key generator to pass to list.sort Please see the :ref:`examples` for a quick start guide, or the :ref:`api` for more details. Installation ------------ Installation of :mod:`natsort` is ultra-easy. Simply execute from the command line:: easy_install natsort or, if you have ``pip`` (preferred over ``easy_install``):: pip install natsort Both of the above commands will download the source for you. You can also download the source from http://pypi.python.org/pypi/natsort, or browse the git repository at https://github.com/SethMMorton/natsort. If you choose to install from source, you can unzip the source archive and enter the directory, and type:: python setup.py install If you wish to run the unit tests, enter:: python setup.py test If you want to build this documentation, enter:: python setup.py build_sphinx :mod:`natsort` requires Python version 2.7 or greater or Python 3.2 or greater. The most efficient sorting can occur if you install the `fastnumbers `_ package (it helps with the string to number conversions.) ``natsort`` will still run (efficiently) without the package, but if you need to squeeze out that extra juice it is recommended you include this as a dependency. ``natsort`` will not require (or check) that `fastnumbers `_ is installed. On BSD-based systems (this includes Mac OS X), the underlying ``locale`` library can be buggy (please see http://bugs.python.org/issue23195); ``locale`` is used for the ``ns.LOCALE`` option and ``humansorted`` function.. To remedy this, one can 1. Use "\*.ISO8859-1" locale (i.e. 'en_US.ISO8859-1') rather than "\*.UTF-8" locale. These locales do not suffer from as many problems as "UTF-8" and thus should give expected results. 2. Use `PyICU `_. If `PyICU `_ is installed, ``natsort`` will use it under the hood; this will give more reliable cross-platform results in the long run. ``natsort`` will not require (or check) that `PyICU `_ is installed at installation. Please visit https://github.com/SethMMorton/natsort/issues/21 for more details and how to install on Mac OS X. **Please note** that using `PyICU `_ is the only way to guarantee correct results for all input on BSD-based systems, since every other suggestion is a workaround. 3. Do nothing. As of ``natsort`` version 4.0.0, ``natsort`` is configured to compensate for a broken ``locale`` library in terms of case-handling; if you do not need to be able to properly handle non-ASCII characters then this may be the best option for you. Note that the above solutions *should not* be required for Windows or Linux since in Linux-based systems and Windows systems ``locale`` *should* work just fine. :mod:`natsort` comes with a shell script called :mod:`natsort`, or can also be called from the command line with ``python -m natsort``. The command line script is only installed onto your ``PATH`` if you don't install via a wheel. There is apparently a known bug with the wheel installation process that will not create entry points. natsort-4.0.3/docs/source/natsort_keygen.rst000066400000000000000000000002261254301707600212330ustar00rootroot00000000000000.. default-domain:: py .. currentmodule:: natsort :func:`~natsort.natsort_keygen` =============================== .. autofunction:: natsort_keygen natsort-4.0.3/docs/source/natsorted.rst000066400000000000000000000002071254301707600202010ustar00rootroot00000000000000.. default-domain:: py .. currentmodule:: natsort :func:`~natsort.natsorted` ========================== .. autofunction:: natsorted natsort-4.0.3/docs/source/ns_class.rst000066400000000000000000000001611254301707600200020ustar00rootroot00000000000000.. default-domain:: py .. currentmodule:: natsort :class:`~natsort.ns` ==================== .. autoclass:: ns natsort-4.0.3/docs/source/order_by_index.rst000066400000000000000000000002261254301707600211730ustar00rootroot00000000000000.. default-domain:: py .. currentmodule:: natsort :func:`~natsort.order_by_index` =============================== .. autofunction:: order_by_index natsort-4.0.3/docs/source/realsorted.rst000066400000000000000000000002121254301707600203360ustar00rootroot00000000000000.. default-domain:: py .. currentmodule:: natsort :func:`~natsort.realsorted` =========================== .. autofunction:: realsorted natsort-4.0.3/docs/source/shell.rst000066400000000000000000000134071254301707600173130ustar00rootroot00000000000000.. default-domain:: py .. currentmodule:: natsort .. _shell: Shell Script ============ The ``natsort`` shell script is automatically installed when you install :mod:`natsort` with pip. Below is the usage and some usage examples for the ``natsort`` shell script. Usage ----- :: usage: natsort [-h] [--version] [-p] [-f LOW HIGH] [-F LOW HIGH] [-e EXCLUDE] [-r] [-t {digit,int,float,version,ver}] [--nosign] [--noexp] [--locale] [entries [entries ...]] Performs a natural sort on entries given on the command-line. A natural sort sorts numerically then alphabetically, and will sort by numbers in the middle of an entry. positional arguments: entries The entries to sort. Taken from stdin if nothing is given on the command line. optional arguments: -h, --help show this help message and exit --version show program's version number and exit -p, --paths Interpret the input as file paths. This is not strictly necessary to sort all file paths, but in cases where there are OS-generated file paths like "Folder/" and "Folder (1)/", this option is needed to make the paths sorted in the order you expect ("Folder/" before "Folder (1)/"). -f LOW HIGH, --filter LOW HIGH Used for keeping only the entries that have a number falling in the given range. -F LOW HIGH, --reverse-filter LOW HIGH Used for excluding the entries that have a number falling in the given range. -e EXCLUDE, --exclude EXCLUDE Used to exclude an entry that contains a specific number. -r, --reverse Returns in reversed order. -t {digit,int,float,version,ver,real,f,i,r,d}, --number-type {digit,int,float,version,ver,real,f,i,r,d}, --number_type {digit,int,float,version,ver,real,f,i,r,d} Choose the type of number to search for. "float" will search for floating-point numbers. "int" will only search for integers. "digit", "version", and "ver" are synonyms for "int"."real" is a shortcut for "float" with --sign. "i" and "d" are synonyms for "int", "f" is a synonym for "float", and "r" is a synonym for "real".The default is int. --nosign Do not consider "+" or "-" as part of a number, i.e. do not take sign into consideration. This is the default. -s, --sign Consider "+" or "-" as part of a number, i.e. take sign into consideration. The default is unsigned. --noexp Do not consider an exponential as part of a number, i.e. 1e4, would be considered as 1, "e", and 4, not as 10000. This only effects the --number-type=float. -l, --locale Causes natsort to use locale-aware sorting. You will get the best results if you install PyICU. Description ----------- ``natsort`` was originally written to aid in computational chemistry research so that it would be easy to analyze large sets of output files named after the parameter used:: $ ls *.out mode1000.35.out mode1243.34.out mode744.43.out mode943.54.out (Obviously, in reality there would be more files, but you get the idea.) Notice that the shell sorts in lexicographical order. This is the behavior of programs like ``find`` as well as ``ls``. The problem is passing these files to an analysis program causes them not to appear in numerical order, which can lead to bad analysis. To remedy this, use ``natsort``:: $ natsort *.out mode744.43.out mode943.54.out mode1000.35.out mode1243.34.out $ natsort -t r *.out | xargs your_program ``-t r`` is short for ``--number-type real``. You can also place natsort in the middle of a pipe:: $ find . -name "*.out" | natsort -t r | xargs your_program To sort version numbers, use the default ``--number-type``:: $ ls * prog-1.10.zip prog-1.9.zip prog-2.0.zip $ natsort * prog-1.9.zip prog-1.10.zip prog-2.0.zip In general, all ``natsort`` shell script options mirror the :func:`~natsorted` API, with notable exception of the ``--filter``, ``--reverse-filter``, and ``--exclude`` options. These three options are used as follows:: $ ls *.out mode1000.35.out mode1243.34.out mode744.43.out mode943.54.out $ natsort -t r *.out -f 900 1100 # Select only numbers between 900-1100 mode943.54.out mode1000.35.out $ natsort -t r *.out -F 900 1100 # Select only numbers NOT between 900-1100 mode744.43.out mode1243.34.out $ natsort -t r *.out -e 1000.35 # Exclude 1000.35 from search mode744.43.out mode943.54.out mode1243.34.out If you are sorting paths with OS-generated filenames, you may require the ``--paths``/``-p`` option:: $ find . ! -path . -type f ./folder/file (1).txt ./folder/file.txt ./folder (1)/file.txt ./folder (10)/file.txt ./folder (2)/file.txt $ find . ! -path . -type f | natsort ./folder (1)/file.txt ./folder (2)/file.txt ./folder (10)/file.txt ./folder/file (1).txt ./folder/file.txt $ find . ! -path . -type f | natsort -p ./folder/file.txt ./folder/file (1).txt ./folder (1)/file.txt ./folder (2)/file.txt ./folder (10)/file.txt natsort-4.0.3/docs/source/solar/000077500000000000000000000000001254301707600165655ustar00rootroot00000000000000natsort-4.0.3/docs/source/solar/NEWS.txt000066400000000000000000000013771254301707600201120ustar00rootroot00000000000000News ==== 1.3 --- * Release date: 2012-11-01. * Source Code Pro is now used for code samples. * Reduced font size of pre elements. * Horizontal rule for header elements. * HTML pre contents are now wrapped (no scrollbars). * Changed permalink color from black to a lighter one. 1.2 --- * Release date: 2012-10-03. * Style additional admonition levels. * Increase padding for navigation links (minor). * Add shadow for admonition items (minor). 1.1 --- * Release date: 2012-09-05. * Add a new background. * Revert font of headings to Open Sans Light. * Darker color for h3 - h6. * Removed dependency on solarized dark pygments style. * Nice looking scrollbars for pre element. 1.0 --- * Release date: 2012-08-24. * Initial release. natsort-4.0.3/docs/source/solar/README.rst000066400000000000000000000015541254301707600202610ustar00rootroot00000000000000Solar theme for Python Sphinx ============================= Solar is an attempt to create a theme for Sphinx based on the `Solarized `_ color scheme. Preview ------- http://vimalkumar.in/sphinx-themes/solar Download -------- Released versions are available from http://github.com/vkvn/sphinx-themes/downloads Installation ------------ #. Extract the archive. #. Modify ``conf.py`` of an existing Sphinx project or create new project using ``sphinx-quickstart``. #. Change the ``html_theme`` parameter to ``solar``. #. Change the ``html_theme_path`` to the location containing the extracted archive. License ------- `GNU General Public License `_. Credits ------- Modified from the default Sphinx theme -- Sphinxdoc Background pattern from http://subtlepatterns.com. natsort-4.0.3/docs/source/solar/layout.html000066400000000000000000000024031254301707600207670ustar00rootroot00000000000000{% extends "basic/layout.html" %} {%- block doctype -%} {%- endblock -%} {%- block extrahead -%} {%- endblock -%} {# put the sidebar before the body #} {% block sidebar1 %}{{ sidebar() }}{% endblock %} {% block sidebar2 %}{% endblock %} {%- block footer %} {%- endblock %} natsort-4.0.3/docs/source/solar/static/000077500000000000000000000000001254301707600200545ustar00rootroot00000000000000natsort-4.0.3/docs/source/solar/static/solar.css000066400000000000000000000144641254301707600217170ustar00rootroot00000000000000/* solar.css * Modified from sphinxdoc.css of the sphinxdoc theme. */ @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'Open Sans', sans-serif; font-size: 14px; line-height: 150%; text-align: center; color: #002b36; padding: 0; margin: 0px 80px 0px 80px; min-width: 740px; -moz-box-shadow: 0px 0px 10px #93a1a1; -webkit-box-shadow: 0px 0px 10px #93a1a1; box-shadow: 0px 0px 10px #93a1a1; background: url("subtle_dots.png") repeat; } div.document { background-color: #fcfcfc; text-align: left; background-repeat: repeat-x; } div.bodywrapper { margin: 0 240px 0 0; border-right: 1px dotted #eee8d5; } div.body { background-color: white; margin: 0; padding: 0.5em 20px 20px 20px; } div.related { font-size: 1em; background: #002b36; color: #839496; padding: 5px 0px; } div.related ul { height: 2em; margin: 2px; } div.related ul li { margin: 0; padding: 0; height: 2em; float: left; } div.related ul li.right { float: right; margin-right: 5px; } div.related ul li a { margin: 0; padding: 2px 5px; line-height: 2em; text-decoration: none; color: #839496; } div.related ul li a:hover { background-color: #073642; -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; } div.sphinxsidebarwrapper { padding: 0; } div.sphinxsidebar { margin: 0; padding: 0.5em 15px 15px 0; width: 210px; float: right; font-size: 0.9em; text-align: left; } div.sphinxsidebar h3, div.sphinxsidebar h4 { margin: 1em 0 0.5em 0; font-size: 1em; padding: 0.7em; background-color: #eeeff1; } div.sphinxsidebar h3 a { color: #2E3436; } div.sphinxsidebar ul { padding-left: 1.5em; margin-top: 7px; padding: 0; line-height: 150%; color: #586e75; } div.sphinxsidebar ul ul { margin-left: 20px; } div.sphinxsidebar input { border: 1px solid #eee8d5; } div.footer { background-color: #93a1a1; color: #eee; padding: 3px 8px 3px 0; clear: both; font-size: 0.8em; text-align: right; } div.footer a { color: #eee; text-decoration: none; } /* -- body styles ----------------------------------------------------------- */ p { margin: 0.8em 0 0.5em 0; } div.body a, div.sphinxsidebarwrapper a { color: #268bd2; text-decoration: none; } div.body a:hover, div.sphinxsidebarwrapper a:hover { border-bottom: 1px solid #268bd2; } h1, h2, h3, h4, h5, h6 { font-family: "Open Sans", sans-serif; font-weight: 300; } h1 { margin: 0; padding: 0.7em 0 0.3em 0; line-height: 1.2em; color: #002b36; text-shadow: #eee 0.1em 0.1em 0.1em; } h2 { margin: 1.3em 0 0.2em 0; padding: 0 0 10px 0; color: #073642; border-bottom: 1px solid #eee; } h3 { margin: 1em 0 -0.3em 0; padding-bottom: 5px; } h3, h4, h5, h6 { color: #073642; border-bottom: 1px dotted #eee; } div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a { color: #657B83!important; } h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor { display: none; margin: 0 0 0 0.3em; padding: 0 0.2em 0 0.2em; color: #aaa!important; } h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor { display: inline; } h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover, h5 a.anchor:hover, h6 a.anchor:hover { color: #777; background-color: #eee; } a.headerlink { color: #c60f0f!important; font-size: 1em; margin-left: 6px; padding: 0 4px 0 4px; text-decoration: none!important; } a.headerlink:hover { background-color: #ccc; color: white!important; } cite, code, tt { font-family: 'Source Code Pro', monospace; font-size: 0.9em; letter-spacing: 0.01em; background-color: #eeeff2; font-style: normal; } hr { border: 1px solid #eee; margin: 2em; } .highlight { -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; } pre { font-family: 'Source Code Pro', monospace; font-style: normal; font-size: 0.9em; letter-spacing: 0.015em; line-height: 120%; padding: 0.7em; white-space: pre-wrap; /* css-3 */ white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ white-space: -pre-wrap; /* Opera 4-6 */ white-space: -o-pre-wrap; /* Opera 7 */ word-wrap: break-word; /* Internet Explorer 5.5+ */ } pre a { color: inherit; text-decoration: underline; } td.linenos pre { padding: 0.5em 0; } div.quotebar { background-color: #f8f8f8; max-width: 250px; float: right; padding: 2px 7px; border: 1px solid #ccc; } div.topic { background-color: #f8f8f8; } table { border-collapse: collapse; margin: 0 -0.5em 0 -0.5em; } table td, table th { padding: 0.2em 0.5em 0.2em 0.5em; } div.admonition { font-size: 0.9em; margin: 1em 0 1em 0; border: 1px solid #eee; background-color: #f7f7f7; padding: 0; -moz-box-shadow: 0px 8px 6px -8px #93a1a1; -webkit-box-shadow: 0px 8px 6px -8px #93a1a1; box-shadow: 0px 8px 6px -8px #93a1a1; } div.admonition p { margin: 0.5em 1em 0.5em 1em; padding: 0.2em; } div.admonition pre { margin: 0.4em 1em 0.4em 1em; } div.admonition p.admonition-title { margin: 0; padding: 0.2em 0 0.2em 0.6em; color: white; border-bottom: 1px solid #eee8d5; font-weight: bold; background-color: #268bd2; } div.warning p.admonition-title, div.important p.admonition-title { background-color: #cb4b16; } div.hint p.admonition-title, div.tip p.admonition-title { background-color: #859900; } div.caution p.admonition-title, div.attention p.admonition-title, div.danger p.admonition-title, div.error p.admonition-title { background-color: #dc322f; } div.admonition ul, div.admonition ol { margin: 0.1em 0.5em 0.5em 3em; padding: 0; } div.versioninfo { margin: 1em 0 0 0; border: 1px solid #eee; background-color: #DDEAF0; padding: 8px; line-height: 1.3em; font-size: 0.9em; } div.viewcode-block:target { background-color: #f4debf; border-top: 1px solid #eee; border-bottom: 1px solid #eee; } natsort-4.0.3/docs/source/solar/static/solarized-dark.css000066400000000000000000000101531254301707600235010ustar00rootroot00000000000000/* solarized dark style for solar theme */ /*style pre scrollbar*/ pre::-webkit-scrollbar, .highlight::-webkit-scrollbar { height: 0.5em; background: #073642; } pre::-webkit-scrollbar-thumb { border-radius: 1em; background: #93a1a1; } /* pygments style */ .highlight .hll { background-color: #ffffcc } .highlight { background: #002B36!important; color: #93A1A1 } .highlight .c { color: #586E75 } /* Comment */ .highlight .err { color: #93A1A1 } /* Error */ .highlight .g { color: #93A1A1 } /* Generic */ .highlight .k { color: #859900 } /* Keyword */ .highlight .l { color: #93A1A1 } /* Literal */ .highlight .n { color: #93A1A1 } /* Name */ .highlight .o { color: #859900 } /* Operator */ .highlight .x { color: #CB4B16 } /* Other */ .highlight .p { color: #93A1A1 } /* Punctuation */ .highlight .cm { color: #586E75 } /* Comment.Multiline */ .highlight .cp { color: #859900 } /* Comment.Preproc */ .highlight .c1 { color: #586E75 } /* Comment.Single */ .highlight .cs { color: #859900 } /* Comment.Special */ .highlight .gd { color: #2AA198 } /* Generic.Deleted */ .highlight .ge { color: #93A1A1; font-style: italic } /* Generic.Emph */ .highlight .gr { color: #DC322F } /* Generic.Error */ .highlight .gh { color: #CB4B16 } /* Generic.Heading */ .highlight .gi { color: #859900 } /* Generic.Inserted */ .highlight .go { color: #93A1A1 } /* Generic.Output */ .highlight .gp { color: #93A1A1 } /* Generic.Prompt */ .highlight .gs { color: #93A1A1; font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #CB4B16 } /* Generic.Subheading */ .highlight .gt { color: #93A1A1 } /* Generic.Traceback */ .highlight .kc { color: #CB4B16 } /* Keyword.Constant */ .highlight .kd { color: #268BD2 } /* Keyword.Declaration */ .highlight .kn { color: #859900 } /* Keyword.Namespace */ .highlight .kp { color: #859900 } /* Keyword.Pseudo */ .highlight .kr { color: #268BD2 } /* Keyword.Reserved */ .highlight .kt { color: #DC322F } /* Keyword.Type */ .highlight .ld { color: #93A1A1 } /* Literal.Date */ .highlight .m { color: #2AA198 } /* Literal.Number */ .highlight .s { color: #2AA198 } /* Literal.String */ .highlight .na { color: #93A1A1 } /* Name.Attribute */ .highlight .nb { color: #B58900 } /* Name.Builtin */ .highlight .nc { color: #268BD2 } /* Name.Class */ .highlight .no { color: #CB4B16 } /* Name.Constant */ .highlight .nd { color: #268BD2 } /* Name.Decorator */ .highlight .ni { color: #CB4B16 } /* Name.Entity */ .highlight .ne { color: #CB4B16 } /* Name.Exception */ .highlight .nf { color: #268BD2 } /* Name.Function */ .highlight .nl { color: #93A1A1 } /* Name.Label */ .highlight .nn { color: #93A1A1 } /* Name.Namespace */ .highlight .nx { color: #93A1A1 } /* Name.Other */ .highlight .py { color: #93A1A1 } /* Name.Property */ .highlight .nt { color: #268BD2 } /* Name.Tag */ .highlight .nv { color: #268BD2 } /* Name.Variable */ .highlight .ow { color: #859900 } /* Operator.Word */ .highlight .w { color: #93A1A1 } /* Text.Whitespace */ .highlight .mf { color: #2AA198 } /* Literal.Number.Float */ .highlight .mh { color: #2AA198 } /* Literal.Number.Hex */ .highlight .mi { color: #2AA198 } /* Literal.Number.Integer */ .highlight .mo { color: #2AA198 } /* Literal.Number.Oct */ .highlight .sb { color: #586E75 } /* Literal.String.Backtick */ .highlight .sc { color: #2AA198 } /* Literal.String.Char */ .highlight .sd { color: #93A1A1 } /* Literal.String.Doc */ .highlight .s2 { color: #2AA198 } /* Literal.String.Double */ .highlight .se { color: #CB4B16 } /* Literal.String.Escape */ .highlight .sh { color: #93A1A1 } /* Literal.String.Heredoc */ .highlight .si { color: #2AA198 } /* Literal.String.Interpol */ .highlight .sx { color: #2AA198 } /* Literal.String.Other */ .highlight .sr { color: #DC322F } /* Literal.String.Regex */ .highlight .s1 { color: #2AA198 } /* Literal.String.Single */ .highlight .ss { color: #2AA198 } /* Literal.String.Symbol */ .highlight .bp { color: #268BD2 } /* Name.Builtin.Pseudo */ .highlight .vc { color: #268BD2 } /* Name.Variable.Class */ .highlight .vg { color: #268BD2 } /* Name.Variable.Global */ .highlight .vi { color: #268BD2 } /* Name.Variable.Instance */ .highlight .il { color: #2AA198 } /* Literal.Number.Integer.Long */ natsort-4.0.3/docs/source/solar/static/subtle_dots.png000066400000000000000000000020011254301707600231020ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<"iTXtXML:com.adobe.xmp <uIDATxbx02 D H"U 0}BfB?2 `@Mnvacb* BLx_FBP?IP/$`dU֭s:HIENDB`natsort-4.0.3/docs/source/solar/theme.conf000066400000000000000000000001051254301707600205320ustar00rootroot00000000000000[theme] inherit = basic stylesheet = solar.css pygments_style = none natsort-4.0.3/docs/source/versorted.rst000066400000000000000000000002071254301707600202130ustar00rootroot00000000000000.. default-domain:: py .. currentmodule:: natsort :func:`~natsort.versorted` ========================== .. autofunction:: versorted natsort-4.0.3/natsort/000077500000000000000000000000001254301707600147075ustar00rootroot00000000000000natsort-4.0.3/natsort/__init__.py000066400000000000000000000014041254301707600170170ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import ( print_function, division, unicode_literals, absolute_import ) # Local imports. from natsort.natsort import ( natsort_key, natsort_keygen, natsorted, versorted, humansorted, realsorted, index_natsorted, index_versorted, index_humansorted, index_realsorted, order_by_index, decoder, as_ascii, as_utf8, ns, ) from natsort._version import __version__ __all__ = [ 'natsort_key', 'natsort_keygen', 'natsorted', 'versorted' 'humansorted', 'realsorted', 'index_natsorted', 'index_versorted', 'index_humansorted', 'index_realsorted', 'order_by_index', 'decoder', 'as_ascii', 'as_utf8', 'ns', ] natsort-4.0.3/natsort/__main__.py000066400000000000000000000202221254301707600167770ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import ( print_function, division, unicode_literals, absolute_import ) # Std. lib imports. import sys # Local imports. from natsort.natsort import natsorted, ns from natsort.utils import _regex_and_num_function_chooser from natsort._version import __version__ from natsort.compat.py23 import py23_str def main(): """\ Performs a natural sort on entries given on the command-line. A natural sort sorts numerically then alphabetically, and will sort by numbers in the middle of an entry. """ from argparse import ArgumentParser, RawDescriptionHelpFormatter from textwrap import dedent parser = ArgumentParser(description=dedent(main.__doc__), formatter_class=RawDescriptionHelpFormatter) parser.add_argument('--version', action='version', version='%(prog)s {0}'.format(__version__)) parser.add_argument( '-p', '--paths', default=False, action='store_true', help='Interpret the input as file paths. This is not ' 'strictly necessary to sort all file paths, but in cases ' 'where there are OS-generated file paths like "Folder/" ' 'and "Folder (1)/", this option is needed to make the ' 'paths sorted in the order you expect ("Folder/" before ' '"Folder (1)/").') parser.add_argument( '-f', '--filter', nargs=2, type=float, metavar=('LOW', 'HIGH'), action='append', help='Used for keeping only the entries that have a number ' 'falling in the given range.') parser.add_argument( '-F', '--reverse-filter', nargs=2, type=float, metavar=('LOW', 'HIGH'), action='append', dest='reverse_filter', help='Used for excluding the entries that have a number ' 'falling in the given range.') parser.add_argument( '-e', '--exclude', type=float, action='append', help='Used to exclude an entry that contains a specific number.') parser.add_argument( '-r', '--reverse', action='store_true', default=False, help='Returns in reversed order.') parser.add_argument( '-t', '--number-type', '--number_type', dest='number_type', choices=('digit', 'int', 'float', 'version', 'ver', 'real', 'f', 'i', 'r', 'd'), default='int', help='Choose the type of number to search for. "float" will search ' 'for floating-point numbers. "int" will only search for ' 'integers. "digit", "version", and "ver" are synonyms for "int".' '"real" is a shortcut for "float" with --sign. ' '"i" and "d" are synonyms for "int", "f" is a synonym for ' '"float", and "r" is a synonym for "real".' 'The default is %(default)s.') parser.add_argument( '--nosign', default=False, action='store_false', dest='signed', help='Do not consider "+" or "-" as part of a number, i.e. do not ' 'take sign into consideration. This is the default.') parser.add_argument( '-s', '--sign', default=False, action='store_true', dest='signed', help='Consider "+" or "-" as part of a number, i.e. ' 'take sign into consideration. The default is unsigned.') parser.add_argument( '--noexp', default=True, action='store_false', dest='exp', help='Do not consider an exponential as part of a number, i.e. 1e4, ' 'would be considered as 1, "e", and 4, not as 10000. This only ' 'effects the --number-type=float.') parser.add_argument( '-l', '--locale', action='store_true', default=False, help='Causes natsort to use locale-aware sorting. You will get the ' 'best results if you install PyICU.') parser.add_argument( 'entries', nargs='*', default=sys.stdin, help='The entries to sort. Taken from stdin if nothing is given on ' 'the command line.', ) args = parser.parse_args() # Make sure the filter range is given properly. Does nothing if no filter args.filter = check_filter(args.filter) args.reverse_filter = check_filter(args.reverse_filter) # Remove trailing whitespace from all the entries entries = [e.strip() for e in args.entries] # Sort by directory then by file within directory and print. sort_and_print_entries(entries, args) def range_check(low, high): """\ Verifies that that given range has a low lower than the high. If the condition is not met, a ValueError is raised. Otherwise, the values are returned, but as floats. """ low, high = float(low), float(high) if low >= high: raise ValueError('low >= high') else: return low, high def check_filter(filt): """\ Check that the low value of the filter is lower than the high. If there is to be no filter, return 'None'. If the condition is not met, a ValueError is raised. Otherwise, the values are returned, but as floats. """ # Quick return if no filter. if not filt: return None try: return [range_check(f[0], f[1]) for f in filt] except ValueError as a: raise ValueError('Error in --filter: '+py23_str(a)) def keep_entry_range(entry, lows, highs, converter, regex): """\ Boolean function to determine if an entry should be kept out based on if any numbers are in a given range. Returns True if it should be kept (i.e. falls in the range), and False if it is not in the range and should not be kept. """ return any(low <= converter(num) <= high for num in regex.findall(entry) for low, high in zip(lows, highs)) def exclude_entry(entry, values, converter, regex): """\ Boolean function to determine if an entry should be kept out based on if it contains a specific number. Returns True if it should be kept (i.e. does not match), and False if it matches and should not be kept. """ return not any(converter(num) in values for num in regex.findall(entry)) def sort_and_print_entries(entries, args): """Sort the entries, applying the filters first if necessary.""" # Extract the proper number type. is_float = args.number_type in ('float', 'real', 'f', 'r') signed = args.signed or args.number_type in ('real', 'r') alg = (ns.FLOAT * is_float | ns.SIGNED * signed | ns.NOEXP * (not args.exp) | ns.PATH * args.paths | ns.LOCALE * args.locale) # Pre-remove entries that don't pass the filtering criteria # Make sure we use the same searching algorithm for filtering # as for sorting. do_filter = args.filter is not None or args.reverse_filter is not None if do_filter or args.exclude: inp_options = (ns.FLOAT * is_float | ns.SIGNED * signed | ns.NOEXP * (not args.exp), '.' ) regex, num_function = _regex_and_num_function_chooser[inp_options] if args.filter is not None: lows, highs = ([f[0] for f in args.filter], [f[1] for f in args.filter]) entries = [entry for entry in entries if keep_entry_range(entry, lows, highs, num_function, regex)] if args.reverse_filter is not None: lows, highs = ([f[0] for f in args.reverse_filter], [f[1] for f in args.reverse_filter]) entries = [entry for entry in entries if not keep_entry_range(entry, lows, highs, num_function, regex)] if args.exclude: exclude = set(args.exclude) entries = [entry for entry in entries if exclude_entry(entry, exclude, num_function, regex)] # Print off the sorted results for entry in natsorted(entries, reverse=args.reverse, alg=alg): print(entry) if __name__ == '__main__': try: main() except ValueError as a: sys.exit(py23_str(a)) except KeyboardInterrupt: sys.exit(1) natsort-4.0.3/natsort/_version.py000066400000000000000000000002261254301707600171050ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import ( print_function, division, unicode_literals, absolute_import ) __version__ = '4.0.3' natsort-4.0.3/natsort/compat/000077500000000000000000000000001254301707600161725ustar00rootroot00000000000000natsort-4.0.3/natsort/compat/__init__.py000066400000000000000000000000001254301707600202710ustar00rootroot00000000000000natsort-4.0.3/natsort/compat/fake_fastnumbers.py000066400000000000000000000030571254301707600220700ustar00rootroot00000000000000# -*- coding: utf-8 -*- """\ This module is intended to replicate some of the functionality from the fastnumbers module in the event that module is not installed. """ from __future__ import ( print_function, division, unicode_literals, absolute_import ) # Std. lib imports. import sys import re import unicodedata float_re = re.compile(r'[-+]?(\d*\.?\d+(?:[eE][-+]?\d+)?|inf(?:inity)?|nan)$') if sys.version[0] == '2': int_re = re.compile(r'[-+]?\d+[lL]?$') else: int_re = re.compile(r'[-+]?\d+$') long = int unicode = str def fast_float(x, regex_matcher=float_re.match, uni=unicodedata.numeric): """Convert a string to a float quickly""" if type(x) in (int, long, float): return float(x) elif regex_matcher(x): return float(x) elif type(x) == unicode and len(x) == 1 and uni(x, None) is not None: return uni(x) else: return x def fast_int(x, regex_matcher=int_re.match, uni=unicodedata.digit): """\ Convert a string to a int quickly, return input as-is if not possible. """ if type(x) in (int, long, float): return int(x) elif regex_matcher(x): return int(x.rstrip('Ll')) elif type(x) == unicode and len(x) == 1 and uni(x, None) is not None: return uni(x) else: return x def isfloat(x, num_only=False): """Returns true if the input is a float, false otherwise.""" return type(x) == float def isint(x, num_only=False): """Returns true if the input is an int, false otherwise.""" return type(x) in set([int, long]) natsort-4.0.3/natsort/compat/fastnumbers.py000066400000000000000000000012761254301707600211030ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import ( print_function, division, unicode_literals, absolute_import ) # If the user has fastnumbers installed, they will get great speed # benefits. If not, we use the simulated functions that come with natsort. try: from fastnumbers import ( fast_float, fast_int, isint, isfloat, ) import fastnumbers v = list(map(int, fastnumbers.__version__.split('.'))) if not (v[0] >= 0 and v[1] >= 5): # Require >= version 0.5.0. raise ImportError except ImportError: from natsort.compat.fake_fastnumbers import ( fast_float, fast_int, isint, isfloat, ) natsort-4.0.3/natsort/compat/locale.py000066400000000000000000000027561254301707600200150ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import ( print_function, division, unicode_literals, absolute_import ) # Std. lib imports import sys # Local imports. from natsort.compat.py23 import PY_VERSION, cmp_to_key # Make the strxfrm function from strcoll on Python2 # It can be buggy (especially on BSD-based systems), # so prefer PyICU if available. try: import PyICU from locale import getlocale # If using PyICU, get the locale from the current global locale, # then create a sort key from that def get_pyicu_transform(l, _d={}): if l not in _d: if l == (None, None): c = PyICU.Collator.createInstance(PyICU.Locale()) else: loc = '.'.join(l) c = PyICU.Collator.createInstance(PyICU.Locale(loc)) _d[l] = c.getSortKey return _d[l] use_pyicu = True null_string = b'' def dumb_sort(): return False except ImportError: if sys.version[0] == '2': from locale import strcoll strxfrm = cmp_to_key(strcoll) null_string = strxfrm('') else: from locale import strxfrm null_string = '' use_pyicu = False # On some systems, locale is broken and does not sort in the expected # order. We will try to detect this and compensate. def dumb_sort(): return strxfrm('A') < strxfrm('a') if PY_VERSION >= 3.3: def _low(x): return x.casefold() else: def _low(x): return x.lower() natsort-4.0.3/natsort/compat/pathlib.py000066400000000000000000000005331254301707600201700ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import ( print_function, division, unicode_literals, absolute_import ) try: from pathlib import PurePath # PurePath is the base object for Paths. except ImportError: # pragma: no cover PurePath = object # To avoid NameErrors. has_pathlib = False else: has_pathlib = True natsort-4.0.3/natsort/compat/py23.py000066400000000000000000000061031254301707600173410ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import ( print_function, division, unicode_literals, absolute_import ) import functools import sys # These functions are used to make the doctests compatible between # python2 and python3, and also provide uniform functionality between # the two versions. This code is pretty much lifted from the iPython # project's py3compat.py file. Credit to the iPython devs. # Numeric form of version PY_VERSION = float(sys.version[:3]) # Assume all strings are Unicode in Python 2 py23_str = str if sys.version[0] == '3' else unicode # Use the range iterator always py23_range = range if sys.version[0] == '3' else xrange # Uniform base string type py23_basestring = str if sys.version[0] == '3' else basestring # unichr function py23_unichr = chr if sys.version[0] == '3' else unichr # zip as an iterator if sys.version[0] == '3': py23_zip = zip else: import itertools py23_zip = itertools.izip # cmp_to_key was not created till 2.7, so require this for 2.6 try: from functools import cmp_to_key except ImportError: # pragma: no cover def cmp_to_key(mycmp): """Convert a cmp= function into a key= function""" class K(object): __slots__ = ['obj'] def __init__(self, obj): self.obj = obj def __lt__(self, other): return mycmp(self.obj, other.obj) < 0 def __gt__(self, other): return mycmp(self.obj, other.obj) > 0 def __eq__(self, other): return mycmp(self.obj, other.obj) == 0 def __le__(self, other): return mycmp(self.obj, other.obj) <= 0 def __ge__(self, other): return mycmp(self.obj, other.obj) >= 0 def __ne__(self, other): return mycmp(self.obj, other.obj) != 0 def __hash__(self): raise TypeError('hash not implemented') return K # This function is intended to decorate other functions that will modify # either a string directly, or a function's docstring. def _modify_str_or_docstring(str_change_func): @functools.wraps(str_change_func) def wrapper(func_or_str): if isinstance(func_or_str, py23_basestring): func = None doc = func_or_str else: func = func_or_str doc = func.__doc__ doc = str_change_func(doc) if func: func.__doc__ = doc return func return doc return wrapper # Properly modify a doctstring to either have the unicode literal or not. if sys.version[0] == '3': # Abstract u'abc' syntax: @_modify_str_or_docstring def u_format(s): """"{u}'abc'" --> "'abc'" (Python 3) Accepts a string or a function, so it can be used as a decorator.""" return s.format(u='') else: # Abstract u'abc' syntax: @_modify_str_or_docstring def u_format(s): """"{u}'abc'" --> "u'abc'" (Python 2) Accepts a string or a function, so it can be used as a decorator.""" return s.format(u='u') natsort-4.0.3/natsort/locale_help.py000066400000000000000000000045671254301707600175440ustar00rootroot00000000000000# -*- coding: utf-8 -*- """\ This module is intended to help combine some locale functions together for natsort consumption. It also accounts for Python2 and Python3 differences. """ from __future__ import ( print_function, division, unicode_literals, absolute_import ) # Std. lib imports. from itertools import chain from locale import localeconv # Local imports. from natsort.compat.locale import use_pyicu, _low if use_pyicu: from natsort.compat.locale import get_pyicu_transform, getlocale else: from natsort.compat.locale import strxfrm def groupletters(x): """Double all characters, making doubled letters lowercase.""" return ''.join(chain.from_iterable([_low(y), y] for y in x)) def grouper(val, func): """\ Attempt to convert a string to a number. If the conversion was not possible, run it through the letter grouper to make the sorting work as requested. """ # Return the number or transformed string. # If the input is identical to the output, then no conversion happened. s = func[0](val) return groupletters(s) if not func[1](s) else s def locale_convert(val, func, group): """\ Attempt to convert a string to a number, first converting the decimal place character if needed. Then, if the conversion was not possible (i.e. it is not a number), run it through strxfrm to make the work sorting as requested, possibly grouping first. """ # Format the number so that the conversion function can interpret it. radix = localeconv()['decimal_point'] s = val.replace(radix, '.') if radix != '.' else val # Perform the conversion t = func[0](s) # Return the number or transformed string. # If the input is identical to the output, then no conversion happened. # In this case, we don't want to return the function output because it # may have had characters modified from the above 'replace' call, # so we return the input. if group: if use_pyicu: xfrm = get_pyicu_transform(getlocale()) return xfrm(groupletters(val)) if not func[1](t) else t else: return strxfrm(groupletters(val)) if not func[1](t) else t else: if use_pyicu: xfrm = get_pyicu_transform(getlocale()) return xfrm(val) if not func[1](t) else t else: return strxfrm(val) if not func[1](t) else t natsort-4.0.3/natsort/natsort.py000066400000000000000000000514231254301707600167600ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Natsort can sort strings with numbers in a natural order. It provides the natsorted function to sort strings with arbitrary numbers. You can mix types with natsorted. This can get around the new 'unorderable types' issue with Python 3. Natsort will recursively descend into lists of lists so you can sort by the sublist contents. See the README or the natsort homepage for more details. """ from __future__ import ( print_function, division, unicode_literals, absolute_import ) # Std lib. imports. import re from operator import itemgetter from functools import partial from warnings import warn # Local imports. from natsort.ns_enum import ns from natsort.compat.py23 import u_format from natsort.utils import ( _natsort_key, _args_to_enum, _do_decoding, ) # Make sure the doctest works for either python2 or python3 __doc__ = u_format(__doc__) @u_format def decoder(encoding): """ Return a function that can be used to decode bytes to unicode. Parameters ---------- encoding: str The codec to use for decoding. This must be a valid unicode codec. Returns ------- decode_function: A function that takes a single argument and attempts to decode it using the supplied codec. Any `UnicodeErrors` are raised. If the argument was not of `bytes` type, it is simply returned as-is. See Also -------- as_ascii as_utf8 Examples -------- >>> f = decoder('utf8') >>> f(b'bytes') == 'bytes' True >>> f(12345) == 12345 True >>> natsorted([b'a10', b'a2'], key=decoder('utf8')) == [b'a2', b'a10'] True >>> # On Python 3, without decoder this would return [b'a10', b'a2'] >>> natsorted([b'a10', 'a2'], key=decoder('utf8')) == ['a2', b'a10'] True >>> # On Python 3, without decoder this would raise a TypeError. """ return partial(_do_decoding, encoding=encoding) @u_format def as_ascii(s): """ Function to decode an input with the ASCII codec, or return as-is. Parameters ---------- s: Any object. Returns ------- output: If the input was of type `bytes`, the return value is a `str` decoded with the ASCII codec. Otherwise, the return value is identically the input. See Also -------- decoder """ return _do_decoding(s, 'ascii') @u_format def as_utf8(s): """ Function to decode an input with the UTF-8 codec, or return as-is. Parameters ---------- s: Any object. Returns ------- output: If the input was of type `bytes`, the return value is a `str` decoded with the UTF-8 codec. Otherwise, the return value is identically the input. See Also -------- decoder """ return _do_decoding(s, 'utf-8') def natsort_key(val, key=None, alg=0, **_kwargs): """Undocumented, kept for backwards-compatibility.""" msg = "natsort_key is deprecated as of 3.4.0, please use natsort_keygen" warn(msg, DeprecationWarning) return _natsort_key(val, key, _args_to_enum(**_kwargs) | alg) @u_format def natsort_keygen(key=None, alg=0, **_kwargs): """\ Generate a key to sort strings and numbers naturally. Generate a key to sort strings and numbers naturally, not lexicographically. This key is designed for use as the `key` argument to functions such as the `sorted` builtin. The user may customize the generated function with the arguments to `natsort_keygen`, including an optional `key` function which will be called before the `natsort_key`. Parameters ---------- key : callable, optional A key used to manipulate the input value before parsing for numbers. It is **not** applied recursively. It should accept a single argument and return a single value. alg : ns enum, optional This option is used to control which algorithm `natsort` uses when sorting. For details into these options, please see the :class:`ns` class documentation. The default is `ns.INT`. Returns ------- out : function A wrapped version of the `natsort_key` function that is suitable for passing as the `key` argument to functions such as `sorted`. See Also -------- natsorted Examples -------- `natsort_keygen` is a convenient way to create a custom key to sort lists in-place (for example). Calling with no objects will return a plain `natsort_key` instance:: >>> a = ['num5.10', 'num-3', 'num5.3', 'num2'] >>> a.sort(key=natsort_keygen(alg=ns.REAL)) >>> a [{u}'num-3', {u}'num2', {u}'num5.10', {u}'num5.3'] """ return partial(_natsort_key, key=key, alg=_args_to_enum(**_kwargs) | alg) @u_format def natsorted(seq, key=None, reverse=False, alg=0, **_kwargs): """\ Sorts a sequence naturally. Sorts a sequence naturally (alphabetically and numerically), not lexicographically. Returns a new copy of the sorted sequence as a list. Parameters ---------- seq : iterable The sequence to sort. key : callable, optional A key used to determine how to sort each element of the sequence. It is **not** applied recursively. It should accept a single argument and return a single value. reverse : {{True, False}}, optional Return the list in reversed sorted order. The default is `False`. alg : ns enum, optional This option is used to control which algorithm `natsort` uses when sorting. For details into these options, please see the :class:`ns` class documentation. The default is `ns.INT`. Returns ------- out: list The sorted sequence. See Also -------- natsort_keygen : Generates the key that makes natural sorting possible. realsorted : A wrapper for ``natsorted(seq, alg=ns.REAL)``. humansorted : A wrapper for ``natsorted(seq, alg=ns.LOCALE)``. index_natsorted : Returns the sorted indexes from `natsorted`. Examples -------- Use `natsorted` just like the builtin `sorted`:: >>> a = ['num3', 'num5', 'num2'] >>> natsorted(a) [{u}'num2', {u}'num3', {u}'num5'] """ alg = _args_to_enum(**_kwargs) | alg try: return sorted(seq, reverse=reverse, key=natsort_keygen(key, alg=alg)) except TypeError as e: # pragma: no cover # In the event of an unresolved "unorderable types" error # for string to number type comparisons (not str/bytes), # attempt to sort again, being careful to prevent this error. r = re.compile(r'(?:str|bytes)\(\) [<>] (?:str|bytes)\(\)') if 'unorderable types' in str(e) and not r.search(str(e)): return sorted(seq, reverse=reverse, key=natsort_keygen(key, alg=alg | ns.TYPESAFE)) else: # Re-raise if the problem was not "unorderable types" raise @u_format def versorted(seq, key=None, reverse=False, alg=0, **_kwargs): """\ Identical to :func:`natsorted`. This function exists for backwards compatibility with `natsort` version < 4.0.0. Future development should use :func:`natsorted`. Please see the :func:`natsorted` documentation for use. See Also -------- natsorted """ return natsorted(seq, key, reverse, alg, **_kwargs) @u_format def humansorted(seq, key=None, reverse=False, alg=0): """\ Convenience function to properly sort non-numeric characters. Convenience function to properly sort non-numeric characters in a locale-aware fashion (a.k.a "human sorting"). This is a wrapper around ``natsorted(seq, alg=ns.LOCALE)``. .. warning:: On BSD-based systems (like Mac OS X), the underlying C library that Python's locale module uses is broken. On these systems it is recommended that you install `PyICU `_ if you wish to use ``humansorted``, especially if you need to handle non-ASCII characters. If you are on one of systems and get unexpected results, please try using `PyICU `_ before filing a bug report to `natsort`. Parameters ---------- seq : iterable The sequence to sort. key : callable, optional A key used to determine how to sort each element of the sequence. It is **not** applied recursively. It should accept a single argument and return a single value. reverse : {{True, False}}, optional Return the list in reversed sorted order. The default is `False`. alg : ns enum, optional This option is used to control which algorithm `natsort` uses when sorting. For details into these options, please see the :class:`ns` class documentation. The default is `ns.LOCALE`. Returns ------- out : list The sorted sequence. See Also -------- index_humansorted : Returns the sorted indexes from `humansorted`. Notes ----- You may find that if you do not explicitly set the locale your results may not be as you expect, although as of ``natsort`` version 4.0.0 the sorting algorithm has been updated to account for a buggy ``locale`` installation. In the below example 'en_US.UTF-8' is used, but you should use your locale:: >>> import locale >>> # The 'str' call is only to get around a bug on Python 2.x >>> # where 'setlocale' does not expect unicode strings (ironic, >>> # right?) >>> locale.setlocale(locale.LC_ALL, str('en_US.UTF-8')) 'en_US.UTF-8' It is preferred that you do this before importing `natsort`. If you use `PyICU `_ (see warning above) then you should not need to do explicitly set a locale. Examples -------- Use `humansorted` just like the builtin `sorted`:: >>> a = ['Apple', 'Banana', 'apple', 'banana'] >>> natsorted(a) [{u}'Apple', {u}'Banana', {u}'apple', {u}'banana'] >>> humansorted(a) [{u}'apple', {u}'Apple', {u}'banana', {u}'Banana'] """ return natsorted(seq, key, reverse, alg | ns.LOCALE) @u_format def realsorted(seq, key=None, reverse=False, alg=0): """\ Convenience function to properly sort signed floats. Convenience function to properly sort signed floats within strings (i.e. "a-5.7"). This is a wrapper around ``natsorted(seq, alg=ns.REAL)``. The behavior of :func:`realsorted` for `natsort` version >= 4.0.0 was the default behavior of :func:`natsorted` for `natsort` version < 4.0.0. Parameters ---------- seq : iterable The sequence to sort. key : callable, optional A key used to determine how to sort each element of the sequence. It is **not** applied recursively. It should accept a single argument and return a single value. reverse : {{True, False}}, optional Return the list in reversed sorted order. The default is `False`. alg : ns enum, optional This option is used to control which algorithm `natsort` uses when sorting. For details into these options, please see the :class:`ns` class documentation. The default is `ns.REAL`. Returns ------- out : list The sorted sequence. See Also -------- index_realsorted : Returns the sorted indexes from `realsorted`. Examples -------- Use `realsorted` just like the builtin `sorted`:: >>> a = ['num5.10', 'num-3', 'num5.3', 'num2'] >>> natsorted(a) [{u}'num2', {u}'num5.3', {u}'num5.10', {u}'num-3'] >>> realsorted(a) [{u}'num-3', {u}'num2', {u}'num5.10', {u}'num5.3'] """ return natsorted(seq, key, reverse, alg | ns.REAL) @u_format def index_natsorted(seq, key=None, reverse=False, alg=0, **_kwargs): """\ Return the list of the indexes used to sort the input sequence. Sorts a sequence naturally, but returns a list of sorted the indexes and not the sorted list. This list of indexes can be used to sort multiple lists by the sorted order of the given sequence. Parameters ---------- seq : iterable The sequence to sort. key : callable, optional A key used to determine how to sort each element of the sequence. It is **not** applied recursively. It should accept a single argument and return a single value. reverse : {{True, False}}, optional Return the list in reversed sorted order. The default is `False`. alg : ns enum, optional This option is used to control which algorithm `natsort` uses when sorting. For details into these options, please see the :class:`ns` class documentation. The default is `ns.INT`. Returns ------- out : tuple The ordered indexes of the sequence. See Also -------- natsorted order_by_index Examples -------- Use index_natsorted if you want to sort multiple lists by the sorted order of one list:: >>> a = ['num3', 'num5', 'num2'] >>> b = ['foo', 'bar', 'baz'] >>> index = index_natsorted(a) >>> index [2, 0, 1] >>> # Sort both lists by the sort order of a >>> order_by_index(a, index) [{u}'num2', {u}'num3', {u}'num5'] >>> order_by_index(b, index) [{u}'baz', {u}'foo', {u}'bar'] """ alg = _args_to_enum(**_kwargs) | alg if key is None: newkey = itemgetter(1) else: def newkey(x): return key(itemgetter(1)(x)) # Pair the index and sequence together, then sort by element index_seq_pair = [[x, y] for x, y in enumerate(seq)] try: index_seq_pair.sort(reverse=reverse, key=natsort_keygen(newkey, alg=alg)) except TypeError as e: # pragma: no cover # In the event of an unresolved "unorderable types" error # attempt to sort again, being careful to prevent this error. if 'unorderable types' in str(e): index_seq_pair.sort(reverse=reverse, key=natsort_keygen(newkey, alg=alg | ns.TYPESAFE)) else: # Re-raise if the problem was not "unorderable types" raise return [x for x, _ in index_seq_pair] @u_format def index_versorted(seq, key=None, reverse=False, alg=0, **_kwargs): """\ Identical to :func:`index_natsorted`. This function exists for backwards compatibility with ``index_natsort`` version < 4.0.0. Future development should use :func:`index_natsorted`. Please see the :func:`index_natsorted` documentation for use. See Also -------- index_natsorted """ return index_natsorted(seq, key, reverse, alg, **_kwargs) @u_format def index_humansorted(seq, key=None, reverse=False, alg=0): """\ Return the list of the indexes used to sort the input sequence in a locale-aware manner. Sorts a sequence in a locale-aware manner, but returns a list of sorted the indexes and not the sorted list. This list of indexes can be used to sort multiple lists by the sorted order of the given sequence. This is a wrapper around ``index_natsorted(seq, alg=ns.LOCALE)``. Please see the ``humansorted`` documentation for caveats of using ``index_humansorted``. Parameters ---------- seq: iterable The sequence to sort. key: callable, optional A key used to determine how to sort each element of the sequence. It is **not** applied recursively. It should accept a single argument and return a single value. reverse : {{True, False}}, optional Return the list in reversed sorted order. The default is `False`. alg : ns enum, optional This option is used to control which algorithm `natsort` uses when sorting. For details into these options, please see the :class:`ns` class documentation. The default is `ns.LOCALE`. Returns ------- out : tuple The ordered indexes of the sequence. See Also -------- humansorted order_by_index Notes ----- You may find that if you do not explicitly set the locale your results may not be as you expect, although as of ``natsort`` version 4.0.0 the sorting algorithm has been updated to account for a buggy ``locale`` installation. In the below example 'en_US.UTF-8' is used, but you should use your locale:: >>> import locale >>> # The 'str' call is only to get around a bug on Python 2.x >>> # where 'setlocale' does not expect unicode strings (ironic, >>> # right?) >>> locale.setlocale(locale.LC_ALL, str('en_US.UTF-8')) 'en_US.UTF-8' It is preferred that you do this before importing `natsort`. If you use `PyICU `_ (see warning above) then you should not need to explicitly set a locale. Examples -------- Use `index_humansorted` just like the builtin `sorted`:: >>> a = ['Apple', 'Banana', 'apple', 'banana'] >>> index_humansorted(a) [2, 0, 3, 1] """ return index_natsorted(seq, key, reverse, alg | ns.LOCALE) @u_format def index_realsorted(seq, key=None, reverse=False, alg=0): """\ Return the list of the indexes used to sort the input sequence in a locale-aware manner. Sorts a sequence in a locale-aware manner, but returns a list of sorted the indexes and not the sorted list. This list of indexes can be used to sort multiple lists by the sorted order of the given sequence. This is a wrapper around ``index_natsorted(seq, alg=ns.REAL)``. The behavior of :func:`index_realsorted` in `natsort` version >= 4.0.0 was the default behavior of :func:`index_natsorted` for `natsort` version < 4.0.0. Parameters ---------- seq: iterable The sequence to sort. key: callable, optional A key used to determine how to sort each element of the sequence. It is **not** applied recursively. It should accept a single argument and return a single value. reverse : {{True, False}}, optional Return the list in reversed sorted order. The default is `False`. alg : ns enum, optional This option is used to control which algorithm `natsort` uses when sorting. For details into these options, please see the :class:`ns` class documentation. The default is `ns.REAL`. Returns ------- out : tuple The ordered indexes of the sequence. See Also -------- realsorted order_by_index Examples -------- Use `index_realsorted` just like the builtin `sorted`:: >>> a = ['num5.10', 'num-3', 'num5.3', 'num2'] >>> index_realsorted(a) [1, 3, 0, 2] """ return index_natsorted(seq, key, reverse, alg | ns.REAL) @u_format def order_by_index(seq, index, iter=False): """\ Order a given sequence by an index sequence. The output of `index_natsorted` and `index_versorted` is a sequence of integers (index) that correspond to how its input sequence **would** be sorted. The idea is that this index can be used to reorder multiple sequences by the sorted order of the first sequence. This function is a convenient wrapper to apply this ordering to a sequence. Parameters ---------- seq : iterable The sequence to order. index : iterable The sequence that indicates how to order `seq`. It should be the same length as `seq` and consist of integers only. iter : {{True, False}}, optional If `True`, the ordered sequence is returned as a generator expression; otherwise it is returned as a list. The default is `False`. Returns ------- out : {{list, generator}} The sequence ordered by `index`, as a `list` or as a generator expression (depending on the value of `iter`). See Also -------- index_natsorted index_versorted index_humansorted index_realsorted Examples -------- `order_by_index` is a convenience function that helps you apply the result of `index_natsorted` or `index_versorted`:: >>> a = ['num3', 'num5', 'num2'] >>> b = ['foo', 'bar', 'baz'] >>> index = index_natsorted(a) >>> index [2, 0, 1] >>> # Sort both lists by the sort order of a >>> order_by_index(a, index) [{u}'num2', {u}'num3', {u}'num5'] >>> order_by_index(b, index) [{u}'baz', {u}'foo', {u}'bar'] """ return (seq[i] for i in index) if iter else [seq[i] for i in index] natsort-4.0.3/natsort/ns_enum.py000066400000000000000000000164541254301707600167370ustar00rootroot00000000000000# -*- coding: utf-8 -*- """This module defines the "ns" enum for natsort.""" from __future__ import ( print_function, division, unicode_literals, absolute_import ) class ns(object): """ Enum to control the `natsort` algorithm. This class acts like an enum to control the `natsort` algorithm. The user may select several options simultaneously by or'ing the options together. For example, to choose ``ns.INT``, ``ns.PATH``, and ``ns.LOCALE``, you could do ``ns.INT | ns.LOCALE | ns.PATH``. Each option has a shortened 1- or 2-letter form. .. warning:: On BSD-based systems (like Mac OS X), the underlying C library that Python's locale module uses is broken. On these systems it is recommended that you install `PyICU `_ if you wish to use ``LOCALE``, especially if you need to handle non-ASCII characters. If you are on one of systems and get unexpected results, please try using `PyICU `_ before filing a bug report to ``natsort``. Attributes ---------- INT, I (default) The default - parse numbers as integers. FLOAT, F Tell `natsort` to parse numbers as floats. UNSIGNED, U (default) Tell `natsort` to ignore any sign (i.e. "-" or "+") to the immediate left of a number. It is the same as setting the old `signed` option to `False`. This is the default. SIGNED, S Tell `natsort` to take into account any sign (i.e. "-" or "+") to the immediate left of a number. It is the same as setting the old `signed` option to `True`. VERSION, V This is a shortcut for ``ns.INT | ns.UNSIGNED``, which is useful when attempting to sort version numbers. It is the same as setting the old `number_type` option to `None`. Since ``ns.INT | ns.UNSIGNED`` is default, this is is unnecessary. DIGIT, D Same as `VERSION` above. REAL, R This is a shortcut for ``ns.FLOAT | ns.SIGNED``, which is useful when attempting to sort real numbers. NOEXP, N Tell `natsort` to not search for exponents as part of the number. For example, with `NOEXP` the number "5.6E5" would be interpreted as `5.6`, `"E"`, and `5`. It is the same as setting the old `exp` option to `False`. PATH, P Tell `natsort` to interpret strings as filesystem paths, so they will be split according to the filesystem separator (i.e. '/' on UNIX, '\\' on Windows), as well as splitting on the file extension, if any. Without this, lists of file paths like ``['Folder/', 'Folder (1)/', 'Folder (10)/']`` will not be sorted properly; 'Folder/' will be placed at the end, not at the front. It is the same as setting the old `as_path` option to `True`. LOCALE, L Tell `natsort` to be locale-aware when sorting strings (everything that was not converted to a number). Your sorting results will vary depending on your current locale. Generally, the `GROUPLETTERS` option is not needed with `LOCALE` because the `locale` library groups the letters in the same manner (although you may still need `GROUPLETTERS` if there are numbers in your strings). IGNORECASE, IC Tell `natsort` to ignore case when sorting. For example, ``['Banana', 'apple', 'banana', 'Apple']`` would be sorted as ``['apple', 'Apple', 'Banana', 'banana']``. LOWERCASEFIRST, LF Tell `natsort` to put lowercase letters before uppercase letters when sorting. For example, ``['Banana', 'apple', 'banana', 'Apple']`` would be sorted as ``['apple', 'banana', 'Apple', 'Banana']`` (the default order would be ``['Apple', 'Banana', 'apple', 'banana']`` which is the order from a purely ordinal sort). Useless when used with `IGNORECASE`. Please note that if used with ``LOCALE``, this actually has the reverse effect and will put uppercase first (this is because ``LOCALE`` already puts lowercase first); you may use this to your advantage if you need to modify the order returned with ``LOCALE``. GROUPLETTERS, G Tell `natsort` to group lowercase and uppercase letters together when sorting. For example, ``['Banana', 'apple', 'banana', 'Apple']`` would be sorted as ``['Apple', 'apple', 'Banana', 'banana']``. Useless when used with `IGNORECASE`; use with `LOWERCASEFIRST` to reverse the order of upper and lower case. CAPITALFIRST, C Only used when `LOCALE` is enabled. Tell `natsort` to put all capitalized words before non-capitalized words. This is essentially the inverse of `GROUPLETTERS`, and is the default Python sorting behavior without `LOCALE`. UNGROUPLETTERS, UG An alias for `CAPITALFIRST`. NANLAST, NL If an NaN shows up in the input, this instructs `natsort` to treat these as +Infinity and place them after all the other numbers. By default, an NaN be treated as -Infinity and be placed first. TYPESAFE, T Try hard to avoid "unorderable types" error on Python 3. It is the same as setting the old `py3_safe` option to `True`. This is only needed if using ``SIGNED`` or if sorting by ``FLOAT``. You shouldn't need to use this unless you are using ``natsort_keygen``. *NOTE:* It cannot resolve the ``TypeError`` from trying to compare `str` and `bytes`. Notes ----- If using `LOCALE`, you may find that if you do not explicitly set the locale your results may not be as you expect... I have found that it depends on the system you are on. To do this is straightforward (in the below example I use 'en_US.UTF-8', but you should use your locale):: >>> import locale >>> # The 'str' call is only to get around a bug on Python 2.x >>> # where 'setlocale' does not expect unicode strings (ironic, >>> # right?) >>> locale.setlocale(locale.LC_ALL, str('en_US.UTF-8')) 'en_US.UTF-8' It is preferred that you do this before importing `natsort`. If you use `PyICU `_ (see warning above) then you should not need to do this. """ pass # Sort algorithm "enum" values. _ns = { 'INT': 0, 'I': 0, 'FLOAT': 1, 'F': 1, 'UNSIGNED': 0, 'U': 0, 'SIGNED': 2, 'S': 2, 'VERSION': 0, 'V': 0, # Shortcut for INT | UNSIGNED 'DIGIT': 0, 'D': 0, # Shortcut for INT | UNSIGNED 'REAL': 3, 'R': 3, # Shortcut for FLOAT | SIGNED 'NOEXP': 4, 'N': 4, 'PATH': 8, 'P': 8, 'LOCALE': 16, 'L': 16, 'IGNORECASE': 32, 'IC': 32, 'LOWERCASEFIRST': 64, 'LF': 64, 'GROUPLETTERS': 128, 'G': 128, 'UNGROUPLETTERS': 256, 'UG': 256, 'CAPITALFIRST': 256, 'C': 256, 'NANLAST': 512, 'NL': 512, 'TYPESAFE': 2048, 'T': 2048, } # Populate the ns class with the _ns values. for x, y in _ns.items(): setattr(ns, x, y) natsort-4.0.3/natsort/unicode_numbers.py000066400000000000000000000267221254301707600204530ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Contains all possible non-ASCII unicode numbers. """ from __future__ import ( print_function, division, unicode_literals, absolute_import ) # Std. lib imports. import unicodedata # Local imports. from natsort.compat.py23 import py23_unichr # Rather than determine this on the fly, which would incur a startup # runtime penalty, the hex values of the Unicode numeric characters # are hard-coded below. numeric_hex = [ 0XB2, 0XB3, 0XB9, 0XBC, 0XBD, 0XBE, 0X660, 0X661, 0X662, 0X663, 0X664, 0X665, 0X666, 0X667, 0X668, 0X669, 0X6F0, 0X6F1, 0X6F2, 0X6F3, 0X6F4, 0X6F5, 0X6F6, 0X6F7, 0X6F8, 0X6F9, 0X7C0, 0X7C1, 0X7C2, 0X7C3, 0X7C4, 0X7C5, 0X7C6, 0X7C7, 0X7C8, 0X7C9, 0X966, 0X967, 0X968, 0X969, 0X96A, 0X96B, 0X96C, 0X96D, 0X96E, 0X96F, 0X9E6, 0X9E7, 0X9E8, 0X9E9, 0X9EA, 0X9EB, 0X9EC, 0X9ED, 0X9EE, 0X9EF, 0X9F4, 0X9F5, 0X9F6, 0X9F7, 0X9F8, 0X9F9, 0XA66, 0XA67, 0XA68, 0XA69, 0XA6A, 0XA6B, 0XA6C, 0XA6D, 0XA6E, 0XA6F, 0XAE6, 0XAE7, 0XAE8, 0XAE9, 0XAEA, 0XAEB, 0XAEC, 0XAED, 0XAEE, 0XAEF, 0XB66, 0XB67, 0XB68, 0XB69, 0XB6A, 0XB6B, 0XB6C, 0XB6D, 0XB6E, 0XB6F, 0XB72, 0XB73, 0XB74, 0XB75, 0XB76, 0XB77, 0XBE6, 0XBE7, 0XBE8, 0XBE9, 0XBEA, 0XBEB, 0XBEC, 0XBED, 0XBEE, 0XBEF, 0XBF0, 0XBF1, 0XBF2, 0XC66, 0XC67, 0XC68, 0XC69, 0XC6A, 0XC6B, 0XC6C, 0XC6D, 0XC6E, 0XC6F, 0XC78, 0XC79, 0XC7A, 0XC7B, 0XC7C, 0XC7D, 0XC7E, 0XCE6, 0XCE7, 0XCE8, 0XCE9, 0XCEA, 0XCEB, 0XCEC, 0XCED, 0XCEE, 0XCEF, 0XD66, 0XD67, 0XD68, 0XD69, 0XD6A, 0XD6B, 0XD6C, 0XD6D, 0XD6E, 0XD6F, 0XD70, 0XD71, 0XD72, 0XD73, 0XD74, 0XD75, 0XE50, 0XE51, 0XE52, 0XE53, 0XE54, 0XE55, 0XE56, 0XE57, 0XE58, 0XE59, 0XED0, 0XED1, 0XED2, 0XED3, 0XED4, 0XED5, 0XED6, 0XED7, 0XED8, 0XED9, 0XF20, 0XF21, 0XF22, 0XF23, 0XF24, 0XF25, 0XF26, 0XF27, 0XF28, 0XF29, 0XF2A, 0XF2B, 0XF2C, 0XF2D, 0XF2E, 0XF2F, 0XF30, 0XF31, 0XF32, 0XF33, 0X1040, 0X1041, 0X1042, 0X1043, 0X1044, 0X1045, 0X1046, 0X1047, 0X1048, 0X1049, 0X1090, 0X1091, 0X1092, 0X1093, 0X1094, 0X1095, 0X1096, 0X1097, 0X1098, 0X1099, 0X1369, 0X136A, 0X136B, 0X136C, 0X136D, 0X136E, 0X136F, 0X1370, 0X1371, 0X1372, 0X1373, 0X1374, 0X1375, 0X1376, 0X1377, 0X1378, 0X1379, 0X137A, 0X137B, 0X137C, 0X16EE, 0X16EF, 0X16F0, 0X17E0, 0X17E1, 0X17E2, 0X17E3, 0X17E4, 0X17E5, 0X17E6, 0X17E7, 0X17E8, 0X17E9, 0X17F0, 0X17F1, 0X17F2, 0X17F3, 0X17F4, 0X17F5, 0X17F6, 0X17F7, 0X17F8, 0X17F9, 0X1810, 0X1811, 0X1812, 0X1813, 0X1814, 0X1815, 0X1816, 0X1817, 0X1818, 0X1819, 0X1946, 0X1947, 0X1948, 0X1949, 0X194A, 0X194B, 0X194C, 0X194D, 0X194E, 0X194F, 0X19D0, 0X19D1, 0X19D2, 0X19D3, 0X19D4, 0X19D5, 0X19D6, 0X19D7, 0X19D8, 0X19D9, 0X19DA, 0X1A80, 0X1A81, 0X1A82, 0X1A83, 0X1A84, 0X1A85, 0X1A86, 0X1A87, 0X1A88, 0X1A89, 0X1A90, 0X1A91, 0X1A92, 0X1A93, 0X1A94, 0X1A95, 0X1A96, 0X1A97, 0X1A98, 0X1A99, 0X1B50, 0X1B51, 0X1B52, 0X1B53, 0X1B54, 0X1B55, 0X1B56, 0X1B57, 0X1B58, 0X1B59, 0X1BB0, 0X1BB1, 0X1BB2, 0X1BB3, 0X1BB4, 0X1BB5, 0X1BB6, 0X1BB7, 0X1BB8, 0X1BB9, 0X1C40, 0X1C41, 0X1C42, 0X1C43, 0X1C44, 0X1C45, 0X1C46, 0X1C47, 0X1C48, 0X1C49, 0X1C50, 0X1C51, 0X1C52, 0X1C53, 0X1C54, 0X1C55, 0X1C56, 0X1C57, 0X1C58, 0X1C59, 0X2070, 0X2074, 0X2075, 0X2076, 0X2077, 0X2078, 0X2079, 0X2080, 0X2081, 0X2082, 0X2083, 0X2084, 0X2085, 0X2086, 0X2087, 0X2088, 0X2089, 0X2150, 0X2151, 0X2152, 0X2153, 0X2154, 0X2155, 0X2156, 0X2157, 0X2158, 0X2159, 0X215A, 0X215B, 0X215C, 0X215D, 0X215E, 0X215F, 0X2160, 0X2161, 0X2162, 0X2163, 0X2164, 0X2165, 0X2166, 0X2167, 0X2168, 0X2169, 0X216A, 0X216B, 0X216C, 0X216D, 0X216E, 0X216F, 0X2170, 0X2171, 0X2172, 0X2173, 0X2174, 0X2175, 0X2176, 0X2177, 0X2178, 0X2179, 0X217A, 0X217B, 0X217C, 0X217D, 0X217E, 0X217F, 0X2180, 0X2181, 0X2182, 0X2185, 0X2186, 0X2187, 0X2188, 0X2189, 0X2460, 0X2461, 0X2462, 0X2463, 0X2464, 0X2465, 0X2466, 0X2467, 0X2468, 0X2469, 0X246A, 0X246B, 0X246C, 0X246D, 0X246E, 0X246F, 0X2470, 0X2471, 0X2472, 0X2473, 0X2474, 0X2475, 0X2476, 0X2477, 0X2478, 0X2479, 0X247A, 0X247B, 0X247C, 0X247D, 0X247E, 0X247F, 0X2480, 0X2481, 0X2482, 0X2483, 0X2484, 0X2485, 0X2486, 0X2487, 0X2488, 0X2489, 0X248A, 0X248B, 0X248C, 0X248D, 0X248E, 0X248F, 0X2490, 0X2491, 0X2492, 0X2493, 0X2494, 0X2495, 0X2496, 0X2497, 0X2498, 0X2499, 0X249A, 0X249B, 0X24EA, 0X24EB, 0X24EC, 0X24ED, 0X24EE, 0X24EF, 0X24F0, 0X24F1, 0X24F2, 0X24F3, 0X24F4, 0X24F5, 0X24F6, 0X24F7, 0X24F8, 0X24F9, 0X24FA, 0X24FB, 0X24FC, 0X24FD, 0X24FE, 0X24FF, 0X2776, 0X2777, 0X2778, 0X2779, 0X277A, 0X277B, 0X277C, 0X277D, 0X277E, 0X277F, 0X2780, 0X2781, 0X2782, 0X2783, 0X2784, 0X2785, 0X2786, 0X2787, 0X2788, 0X2789, 0X278A, 0X278B, 0X278C, 0X278D, 0X278E, 0X278F, 0X2790, 0X2791, 0X2792, 0X2793, 0X2CFD, 0X3007, 0X3021, 0X3022, 0X3023, 0X3024, 0X3025, 0X3026, 0X3027, 0X3028, 0X3029, 0X3038, 0X3039, 0X303A, 0X3192, 0X3193, 0X3194, 0X3195, 0X3220, 0X3221, 0X3222, 0X3223, 0X3224, 0X3225, 0X3226, 0X3227, 0X3228, 0X3229, 0X3248, 0X3249, 0X324A, 0X324B, 0X324C, 0X324D, 0X324E, 0X324F, 0X3251, 0X3252, 0X3253, 0X3254, 0X3255, 0X3256, 0X3257, 0X3258, 0X3259, 0X325A, 0X325B, 0X325C, 0X325D, 0X325E, 0X325F, 0X3280, 0X3281, 0X3282, 0X3283, 0X3284, 0X3285, 0X3286, 0X3287, 0X3288, 0X3289, 0X32B1, 0X32B2, 0X32B3, 0X32B4, 0X32B5, 0X32B6, 0X32B7, 0X32B8, 0X32B9, 0X32BA, 0X32BB, 0X32BC, 0X32BD, 0X32BE, 0X32BF, 0X3405, 0X3483, 0X382A, 0X3B4D, 0X4E00, 0X4E03, 0X4E07, 0X4E09, 0X4E5D, 0X4E8C, 0X4E94, 0X4E96, 0X4EBF, 0X4EC0, 0X4EDF, 0X4EE8, 0X4F0D, 0X4F70, 0X5104, 0X5146, 0X5169, 0X516B, 0X516D, 0X5341, 0X5343, 0X5344, 0X5345, 0X534C, 0X53C1, 0X53C2, 0X53C3, 0X53C4, 0X56DB, 0X58F1, 0X58F9, 0X5E7A, 0X5EFE, 0X5EFF, 0X5F0C, 0X5F0D, 0X5F0E, 0X5F10, 0X62FE, 0X634C, 0X67D2, 0X6F06, 0X7396, 0X767E, 0X8086, 0X842C, 0X8CAE, 0X8CB3, 0X8D30, 0X9621, 0X9646, 0X964C, 0X9678, 0X96F6, 0XA620, 0XA621, 0XA622, 0XA623, 0XA624, 0XA625, 0XA626, 0XA627, 0XA628, 0XA629, 0XA6E6, 0XA6E7, 0XA6E8, 0XA6E9, 0XA6EA, 0XA6EB, 0XA6EC, 0XA6ED, 0XA6EE, 0XA6EF, 0XA830, 0XA831, 0XA832, 0XA833, 0XA834, 0XA835, 0XA8D0, 0XA8D1, 0XA8D2, 0XA8D3, 0XA8D4, 0XA8D5, 0XA8D6, 0XA8D7, 0XA8D8, 0XA8D9, 0XA900, 0XA901, 0XA902, 0XA903, 0XA904, 0XA905, 0XA906, 0XA907, 0XA908, 0XA909, 0XA9D0, 0XA9D1, 0XA9D2, 0XA9D3, 0XA9D4, 0XA9D5, 0XA9D6, 0XA9D7, 0XA9D8, 0XA9D9, 0XAA50, 0XAA51, 0XAA52, 0XAA53, 0XAA54, 0XAA55, 0XAA56, 0XAA57, 0XAA58, 0XAA59, 0XABF0, 0XABF1, 0XABF2, 0XABF3, 0XABF4, 0XABF5, 0XABF6, 0XABF7, 0XABF8, 0XABF9, 0XF96B, 0XF973, 0XF978, 0XF9B2, 0XF9D1, 0XF9D3, 0XF9FD, 0XFF10, 0XFF11, 0XFF12, 0XFF13, 0XFF14, 0XFF15, 0XFF16, 0XFF17, 0XFF18, 0XFF19, 0X10107, 0X10108, 0X10109, 0X1010A, 0X1010B, 0X1010C, 0X1010D, 0X1010E, 0X1010F, 0X10110, 0X10111, 0X10112, 0X10113, 0X10114, 0X10115, 0X10116, 0X10117, 0X10118, 0X10119, 0X1011A, 0X1011B, 0X1011C, 0X1011D, 0X1011E, 0X1011F, 0X10120, 0X10121, 0X10122, 0X10123, 0X10124, 0X10125, 0X10126, 0X10127, 0X10128, 0X10129, 0X1012A, 0X1012B, 0X1012C, 0X1012D, 0X1012E, 0X1012F, 0X10130, 0X10131, 0X10132, 0X10133, 0X10140, 0X10141, 0X10142, 0X10143, 0X10144, 0X10145, 0X10146, 0X10147, 0X10148, 0X10149, 0X1014A, 0X1014B, 0X1014C, 0X1014D, 0X1014E, 0X1014F, 0X10150, 0X10151, 0X10152, 0X10153, 0X10154, 0X10155, 0X10156, 0X10157, 0X10158, 0X10159, 0X1015A, 0X1015B, 0X1015C, 0X1015D, 0X1015E, 0X1015F, 0X10160, 0X10161, 0X10162, 0X10163, 0X10164, 0X10165, 0X10166, 0X10167, 0X10168, 0X10169, 0X1016A, 0X1016B, 0X1016C, 0X1016D, 0X1016E, 0X1016F, 0X10170, 0X10171, 0X10172, 0X10173, 0X10174, 0X10175, 0X10176, 0X10177, 0X10178, 0X1018A, 0X10320, 0X10321, 0X10322, 0X10323, 0X10341, 0X1034A, 0X103D1, 0X103D2, 0X103D3, 0X103D4, 0X103D5, 0X104A0, 0X104A1, 0X104A2, 0X104A3, 0X104A4, 0X104A5, 0X104A6, 0X104A7, 0X104A8, 0X104A9, 0X10858, 0X10859, 0X1085A, 0X1085B, 0X1085C, 0X1085D, 0X1085E, 0X1085F, 0X10916, 0X10917, 0X10918, 0X10919, 0X1091A, 0X1091B, 0X10A40, 0X10A41, 0X10A42, 0X10A43, 0X10A44, 0X10A45, 0X10A46, 0X10A47, 0X10A7D, 0X10A7E, 0X10B58, 0X10B59, 0X10B5A, 0X10B5B, 0X10B5C, 0X10B5D, 0X10B5E, 0X10B5F, 0X10B78, 0X10B79, 0X10B7A, 0X10B7B, 0X10B7C, 0X10B7D, 0X10B7E, 0X10B7F, 0X10E60, 0X10E61, 0X10E62, 0X10E63, 0X10E64, 0X10E65, 0X10E66, 0X10E67, 0X10E68, 0X10E69, 0X10E6A, 0X10E6B, 0X10E6C, 0X10E6D, 0X10E6E, 0X10E6F, 0X10E70, 0X10E71, 0X10E72, 0X10E73, 0X10E74, 0X10E75, 0X10E76, 0X10E77, 0X10E78, 0X10E79, 0X10E7A, 0X10E7B, 0X10E7C, 0X10E7D, 0X10E7E, 0X11052, 0X11053, 0X11054, 0X11055, 0X11056, 0X11057, 0X11058, 0X11059, 0X1105A, 0X1105B, 0X1105C, 0X1105D, 0X1105E, 0X1105F, 0X11060, 0X11061, 0X11062, 0X11063, 0X11064, 0X11065, 0X11066, 0X11067, 0X11068, 0X11069, 0X1106A, 0X1106B, 0X1106C, 0X1106D, 0X1106E, 0X1106F, 0X110F0, 0X110F1, 0X110F2, 0X110F3, 0X110F4, 0X110F5, 0X110F6, 0X110F7, 0X110F8, 0X110F9, 0X11136, 0X11137, 0X11138, 0X11139, 0X1113A, 0X1113B, 0X1113C, 0X1113D, 0X1113E, 0X1113F, 0X111D0, 0X111D1, 0X111D2, 0X111D3, 0X111D4, 0X111D5, 0X111D6, 0X111D7, 0X111D8, 0X111D9, 0X116C0, 0X116C1, 0X116C2, 0X116C3, 0X116C4, 0X116C5, 0X116C6, 0X116C7, 0X116C8, 0X116C9, 0X12400, 0X12401, 0X12402, 0X12403, 0X12404, 0X12405, 0X12406, 0X12407, 0X12408, 0X12409, 0X1240A, 0X1240B, 0X1240C, 0X1240D, 0X1240E, 0X1240F, 0X12410, 0X12411, 0X12412, 0X12413, 0X12414, 0X12415, 0X12416, 0X12417, 0X12418, 0X12419, 0X1241A, 0X1241B, 0X1241C, 0X1241D, 0X1241E, 0X1241F, 0X12420, 0X12421, 0X12422, 0X12423, 0X12424, 0X12425, 0X12426, 0X12427, 0X12428, 0X12429, 0X1242A, 0X1242B, 0X1242C, 0X1242D, 0X1242E, 0X1242F, 0X12430, 0X12431, 0X12432, 0X12433, 0X12434, 0X12435, 0X12436, 0X12437, 0X12438, 0X12439, 0X1243A, 0X1243B, 0X1243C, 0X1243D, 0X1243E, 0X1243F, 0X12440, 0X12441, 0X12442, 0X12443, 0X12444, 0X12445, 0X12446, 0X12447, 0X12448, 0X12449, 0X1244A, 0X1244B, 0X1244C, 0X1244D, 0X1244E, 0X1244F, 0X12450, 0X12451, 0X12452, 0X12453, 0X12454, 0X12455, 0X12456, 0X12457, 0X12458, 0X12459, 0X1245A, 0X1245B, 0X1245C, 0X1245D, 0X1245E, 0X1245F, 0X12460, 0X12461, 0X12462, 0X1D360, 0X1D361, 0X1D362, 0X1D363, 0X1D364, 0X1D365, 0X1D366, 0X1D367, 0X1D368, 0X1D369, 0X1D36A, 0X1D36B, 0X1D36C, 0X1D36D, 0X1D36E, 0X1D36F, 0X1D370, 0X1D371, 0X1D7CE, 0X1D7CF, 0X1D7D0, 0X1D7D1, 0X1D7D2, 0X1D7D3, 0X1D7D4, 0X1D7D5, 0X1D7D6, 0X1D7D7, 0X1D7D8, 0X1D7D9, 0X1D7DA, 0X1D7DB, 0X1D7DC, 0X1D7DD, 0X1D7DE, 0X1D7DF, 0X1D7E0, 0X1D7E1, 0X1D7E2, 0X1D7E3, 0X1D7E4, 0X1D7E5, 0X1D7E6, 0X1D7E7, 0X1D7E8, 0X1D7E9, 0X1D7EA, 0X1D7EB, 0X1D7EC, 0X1D7ED, 0X1D7EE, 0X1D7EF, 0X1D7F0, 0X1D7F1, 0X1D7F2, 0X1D7F3, 0X1D7F4, 0X1D7F5, 0X1D7F6, 0X1D7F7, 0X1D7F8, 0X1D7F9, 0X1D7FA, 0X1D7FB, 0X1D7FC, 0X1D7FD, 0X1D7FE, 0X1D7FF, 0X1F100, 0X1F101, 0X1F102, 0X1F103, 0X1F104, 0X1F105, 0X1F106, 0X1F107, 0X1F108, 0X1F109, 0X1F10A, 0X20001, 0X20064, 0X200E2, 0X20121, 0X2092A, 0X20983, 0X2098C, 0X2099C, 0X20AEA, 0X20AFD, 0X20B19, 0X22390, 0X22998, 0X23B1B, 0X2626D, 0X2F890, ] # Convert each hex into the literal Unicode character. # Stop if a ValueError is raised in case of a narrow Unicode build. # The extra check with unicodedata is in case this Python version # does not support some characters. numeric_chars = [] for a in numeric_hex: try: l = py23_unichr(a) except ValueError: break if unicodedata.numeric(l, None) is None: continue numeric_chars.append(l) # The digit characters are a subset of the numerals. digit_chars = [a for a in numeric_chars if unicodedata.digit(a, None) is not None] # Create a single string with the above data. digits = ''.join(digit_chars) numeric = ''.join(numeric_chars) natsort-4.0.3/natsort/utils.py000066400000000000000000000355371254301707600164360ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Utilities and definitions for natsort, mostly all used to define the _natsort_key function. """ from __future__ import ( print_function, division, unicode_literals, absolute_import ) # Std. lib imports. import re from math import isnan from warnings import warn from os import curdir, pardir from os.path import split, splitext from itertools import islice from locale import localeconv # Local imports. from natsort.ns_enum import ns, _ns from natsort.unicode_numbers import digits, numeric from natsort.locale_help import locale_convert, grouper from natsort.compat.pathlib import PurePath, has_pathlib from natsort.compat.py23 import ( py23_str, py23_zip, PY_VERSION, ) from natsort.compat.locale import ( dumb_sort, use_pyicu, null_string, ) from natsort.compat.fastnumbers import ( fast_float, fast_int, isint, isfloat, ) # Group algorithm types for easy extraction _NUMBER_ALGORITHMS = ns.FLOAT | ns.INT | ns.UNSIGNED | ns.SIGNED | ns.NOEXP _ALL_BUT_PATH = (ns.F | ns.I | ns.U | ns.S | ns.N | ns.L | ns.IC | ns.LF | ns.G | ns.UG | ns.TYPESAFE) # The regex that locates floats - include Unicode numerals. _float_sign_exp_re = r'([-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?|[{0}])' _float_sign_exp_re = _float_sign_exp_re.format(numeric) _float_sign_exp_re = re.compile(_float_sign_exp_re, flags=re.U) _float_nosign_exp_re = r'([0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?|[{0}])' _float_nosign_exp_re = _float_nosign_exp_re.format(numeric) _float_nosign_exp_re = re.compile(_float_nosign_exp_re, flags=re.U) _float_sign_noexp_re = r'([-+]?[0-9]*\.?[0-9]+|[{0}])' _float_sign_noexp_re = _float_sign_noexp_re.format(numeric) _float_sign_noexp_re = re.compile(_float_sign_noexp_re, flags=re.U) _float_nosign_noexp_re = r'([0-9]*\.?[0-9]+|[{0}])' _float_nosign_noexp_re = _float_nosign_noexp_re.format(numeric) _float_nosign_noexp_re = re.compile(_float_nosign_noexp_re, flags=re.U) _float_sign_exp_re_c = r'([-+]?[0-9]*[.,]?[0-9]+(?:[eE][-+]?[0-9]+)?)|[{0}]' _float_sign_exp_re_c = _float_sign_exp_re_c.format(numeric) _float_sign_exp_re_c = re.compile(_float_sign_exp_re_c, flags=re.U) _float_nosign_exp_re_c = r'([0-9]*[.,]?[0-9]+(?:[eE][-+]?[0-9]+)?|[{0}])' _float_nosign_exp_re_c = _float_nosign_exp_re_c.format(numeric) _float_nosign_exp_re_c = re.compile(_float_nosign_exp_re_c, flags=re.U) _float_sign_noexp_re_c = r'([-+]?[0-9]*[.,]?[0-9]+|[{0}])' _float_sign_noexp_re_c = _float_sign_noexp_re_c.format(numeric) _float_sign_noexp_re_c = re.compile(_float_sign_noexp_re_c, flags=re.U) _float_nosign_noexp_re_c = r'([0-9]*[.,]?[0-9]+|[{0}])' _float_nosign_noexp_re_c = _float_nosign_noexp_re_c.format(numeric) _float_nosign_noexp_re_c = re.compile(_float_nosign_noexp_re_c, flags=re.U) # Integer regexes - include Unicode digits. _int_nosign_re = r'([0-9]+|[{0}])'.format(digits) _int_nosign_re = re.compile(_int_nosign_re, flags=re.U) _int_sign_re = r'([-+]?[0-9]+|[{0}])'.format(digits) _int_sign_re = re.compile(_int_sign_re, flags=re.U) # This dict will help select the correct regex and number conversion function. _regex_and_num_function_chooser = { (ns.F | ns.S, '.'): (_float_sign_exp_re, fast_float), (ns.F | ns.S | ns.N, '.'): (_float_sign_noexp_re, fast_float), (ns.F | ns.U, '.'): (_float_nosign_exp_re, fast_float), (ns.F | ns.U | ns.N, '.'): (_float_nosign_noexp_re, fast_float), (ns.I | ns.S, '.'): (_int_sign_re, fast_int), (ns.I | ns.S | ns.N, '.'): (_int_sign_re, fast_int), (ns.I | ns.U, '.'): (_int_nosign_re, fast_int), (ns.I | ns.U | ns.N, '.'): (_int_nosign_re, fast_int), (ns.F | ns.S, ','): (_float_sign_exp_re_c, fast_float), (ns.F | ns.S | ns.N, ','): (_float_sign_noexp_re_c, fast_float), (ns.F | ns.U, ','): (_float_nosign_exp_re_c, fast_float), (ns.F | ns.U | ns.N, ','): (_float_nosign_noexp_re_c, fast_float), (ns.I | ns.S, ','): (_int_sign_re, fast_int), (ns.I | ns.S | ns.N, ','): (_int_sign_re, fast_int), (ns.I | ns.U, ','): (_int_nosign_re, fast_int), (ns.I | ns.U | ns.N, ','): (_int_nosign_re, fast_int), } # Dict to select checker function from converter function _conv_to_check = {fast_float: isfloat, fast_int: isint} def _do_decoding(s, encoding): """A function to decode a bytes string, or return the object as-is.""" try: return s.decode(encoding) except UnicodeError: raise except (AttributeError, TypeError): return s def _args_to_enum(**kwargs): """A function to convert input booleans to an enum-type argument.""" alg = 0 keys = ('number_type', 'signed', 'exp', 'as_path', 'py3_safe') if any(x not in keys for x in kwargs): x = set(kwargs) - set(keys) raise TypeError('Invalid argument(s): ' + ', '.join(x)) if 'number_type' in kwargs and kwargs['number_type'] is not int: msg = "The 'number_type' argument is deprecated as of 3.5.0, " msg += "please use 'alg=ns.FLOAT', 'alg=ns.INT', or 'alg=ns.VERSION'" warn(msg, DeprecationWarning) alg |= (_ns['FLOAT'] * bool(kwargs['number_type'] is float)) alg |= (_ns['INT'] * bool(kwargs['number_type'] in (int, None))) alg |= (_ns['SIGNED'] * (kwargs['number_type'] not in (float, None))) if 'signed' in kwargs and kwargs['signed'] is not None: msg = "The 'signed' argument is deprecated as of 3.5.0, " msg += "please use 'alg=ns.SIGNED'." warn(msg, DeprecationWarning) alg |= (_ns['SIGNED'] * bool(kwargs['signed'])) if 'exp' in kwargs and kwargs['exp'] is not None: msg = "The 'exp' argument is deprecated as of 3.5.0, " msg += "please use 'alg=ns.NOEXP'." warn(msg, DeprecationWarning) alg |= (_ns['NOEXP'] * (not kwargs['exp'])) if 'as_path' in kwargs and kwargs['as_path'] is not None: msg = "The 'as_path' argument is deprecated as of 3.5.0, " msg += "please use 'alg=ns.PATH'." warn(msg, DeprecationWarning) alg |= (_ns['PATH'] * kwargs['as_path']) if 'py3_safe' in kwargs and kwargs['py3_safe'] is not None: msg = "The 'py3_safe' argument is deprecated as of 3.5.0, " msg += "please use 'alg=ns.TYPESAFE'." warn(msg, DeprecationWarning) alg |= (_ns['TYPESAFE'] * kwargs['py3_safe']) return alg def _number_extracter(s, regex, numconv, py3_safe, use_locale, group_letters): """Helper to separate the string input into numbers and strings.""" conv_check = (numconv, _conv_to_check[numconv]) # Split the input string by numbers. # If the input is not a string, TypeError is raised. s = regex.split(s) # Now convert the numbers to numbers, and leave strings as strings. # Take into account locale if needed, and group letters if needed. # Remove empty strings from the list. if use_locale: s = [locale_convert(x, conv_check, group_letters) for x in s if x] elif group_letters: s = [grouper(x, conv_check) for x in s if x] else: s = [numconv(x) for x in s if x] # If the list begins with a number, lead with an empty string. # This is used to get around the "unorderable types" issue. if not s: # Return empty list for empty results. return [] elif conv_check[1](s[0], num_only=True): s = [null_string if use_locale else ''] + s # The _py3_safe function inserts "" between numbers in the list, # and is used to get around "unorderable types" in complex cases. # It is a separate function that needs to be requested specifically # because it is expensive to call. return _py3_safe(s, use_locale, conv_check[1]) if py3_safe else s def _path_splitter(s, _d_match=re.compile(r'\.\d').match): """Split a string into its path components. Assumes a string is a path.""" path_parts = [] p_append = path_parts.append # Convert a pathlib PurePath object to a string. if has_pathlib and isinstance(s, PurePath): path_location = str(s) else: # pragma: no cover path_location = s # Continue splitting the path from the back until we have reached # '..' or '.', or until there is nothing left to split. while path_location != curdir and path_location != pardir: parent_path = path_location path_location, child_path = split(parent_path) if path_location == parent_path: break p_append(child_path) # This last append is the base path. # Only append if the string is non-empty. if path_location: p_append(path_location) # We created this list in reversed order, so we now correct the order. path_parts.reverse() # Now, split off the file extensions using a similar method to above. # Continue splitting off file extensions until we reach a decimal number # or there are no more extensions. base = path_parts.pop() base_parts = [] b_append = base_parts.append while True: front = base base, ext = splitext(front) if _d_match(ext) or not ext: # Reset base to before the split if the split is invalid. base = front break b_append(ext) b_append(base) base_parts.reverse() # Return the split parent paths and then the split basename. return path_parts + base_parts def _py3_safe(parsed_list, use_locale, check): """Insert '' between two numbers.""" length = len(parsed_list) if length < 2: return parsed_list else: new_list = [parsed_list[0]] nl_append = new_list.append for before, after in py23_zip(islice(parsed_list, 0, length-1), islice(parsed_list, 1, None)): if check(before, num_only=True) and check(after, num_only=True): nl_append(null_string if use_locale else '') nl_append(after) return new_list 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: 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: 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,) natsort-4.0.3/setup.cfg000066400000000000000000000013751254301707600150440ustar00rootroot00000000000000[bdist_wheel] universal = 1 [sdist] formats = zip,gztar [pytest] flakes-ignore = natsort/compat/py23.py UndefinedName natsort/__init__.py UnusedImport natsort/compat/* UnusedImport docs/source/conf.py ALL test_natsort/test_natsort.py UnusedImport RedefinedWhileUnused test_natsort/test_locale_help.py UnusedImport RedefinedWhileUnused test_natsort/compat/* UnusedImport pep8ignore = natsort/ns_enum.py E126 E241 E123 test_natsort/test_natsort.py E501 E241 E221 test_natsort/test_utils.py E501 E241 E221 test_natsort/test_locale_help.py E501 E241 E221 test_natsort/test_main.py E501 E241 E221 test_natsort/profile_natsorted.py ALL docs/source/conf.py ALL [flake8] max-line-length = 160 ignore = E231,E302 natsort-4.0.3/setup.py000066400000000000000000000067051254301707600147370ustar00rootroot00000000000000#! /usr/bin/env python # Std. lib imports import re import sys from os.path import join # Non-std lib imports from setuptools import setup from setuptools.command.test import test as TestCommand class PyTest(TestCommand): """Custom command to run pytest on all code.""" def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [] self.test_suite = True def run_tests(self): # import here, cause outside the eggs aren't loaded import pytest err1 = pytest.main(['--cov', 'natsort', '--cov-report', 'term-missing', '--flakes', '--pep8', '-s', # '--failed', # '-v', ]) err2 = pytest.main(['--doctest-modules', 'natsort']) err3 = pytest.main(['README.rst', 'docs/source/intro.rst', 'docs/source/examples.rst']) return err1 | err2 | err3 # Read the natsort.py file for the module version number VERSIONFILE = join('natsort', '_version.py') versionsearch = re.compile(r"^__version__ = ['\"]([^'\"]*)['\"]") with open(VERSIONFILE, "rt") as fl: for line in fl: m = versionsearch.search(line) if m: VERSION = m.group(1) break else: s = "Unable to locate version string in {0}" raise RuntimeError(s.format(VERSIONFILE)) # Read in the documentation for the long_description DESCRIPTION = 'Sort lists naturally' try: with open('README.rst') as fl: LONG_DESCRIPTION = fl.read() except IOError: LONG_DESCRIPTION = DESCRIPTION # The argparse module was introduced in python 2.7 or python 3.2 REQUIRES = 'argparse' if sys.version[:3] in ('2.6', '3.0', '3.1') else '' # Testing needs pytest, and mock if less than python 3.3 TESTS_REQUIRE = ['pytest', 'pytest-pep8', 'pytest-flakes', 'pytest-cov', 'pytest-cache', 'hypothesis'] if (sys.version.startswith('2') or (sys.version.startswith('3') and int(sys.version.split('.')[1]) < 3)): TESTS_REQUIRE.append('mock') if (sys.version.startswith('2') or (sys.version.startswith('3') and int(sys.version.split('.')[1]) < 4)): TESTS_REQUIRE.append('pathlib') # The setup parameters setup( name='natsort', version=VERSION, author='Seth M. Morton', author_email='drtuba78@gmail.com', url='https://github.com/SethMMorton/natsort', license='MIT', install_requires=REQUIRES, packages=['natsort', 'natsort.compat'], entry_points={'console_scripts': ['natsort = natsort.__main__:main']}, tests_require=TESTS_REQUIRE, cmdclass={'test': PyTest}, description=DESCRIPTION, long_description=LONG_DESCRIPTION, classifiers=( 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', 'Intended Audience :: System Administrators', 'Intended Audience :: Information Technology', 'Operating System :: OS Independent', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Topic :: Scientific/Engineering :: Information Analysis', 'Topic :: Utilities', ) ) natsort-4.0.3/test_natsort/000077500000000000000000000000001254301707600157465ustar00rootroot00000000000000natsort-4.0.3/test_natsort/compat/000077500000000000000000000000001254301707600172315ustar00rootroot00000000000000natsort-4.0.3/test_natsort/compat/__init__.py000066400000000000000000000000001254301707600213300ustar00rootroot00000000000000natsort-4.0.3/test_natsort/compat/hypothesis.py000066400000000000000000000012601254301707600220010ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import ( print_function, division, unicode_literals, absolute_import ) import sys import compat.mock major_minor = sys.version_info[:2] # Use hypothesis if not on python 2.6. if major_minor != (2, 6): use_hypothesis = True from hypothesis import assume, given, example from hypothesis.specifiers import ( integers_in_range, integers_from, sampled_from, ) # Otherwise mock these imports, because hypothesis # is incompatible with python 2.6. else: example = integers_in_range = integers_from = \ sampled_from = assume = given = compat.mock.MagicMock() use_hypothesis = False natsort-4.0.3/test_natsort/compat/locale.py000066400000000000000000000022311254301707600210400ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import ( print_function, division, unicode_literals, absolute_import ) # Std. lib imports. import locale # Local imports from natsort.locale_help import use_pyicu from natsort.compat.py23 import py23_str def load_locale(x): """ Convenience to load a locale, trying ISO8859-1 first.""" try: locale.setlocale(locale.LC_ALL, str('{0}.ISO8859-1'.format(x))) except: locale.setlocale(locale.LC_ALL, str('{0}.UTF-8'.format(x))) # Check if de_DE is installed. try: load_locale('de_DE') has_locale_de_DE = True except locale.Error: has_locale_de_DE = False # Make a function that will return the appropriate # strxfrm for the current locale. if use_pyicu: from natsort.locale_help import get_pyicu_transform from locale import getlocale def get_strxfrm(): return get_pyicu_transform(getlocale()) else: from natsort.locale_help import strxfrm def get_strxfrm(): return strxfrm # Depending on the python version, use lower or casefold # to make a string lowercase. try: low = py23_str.casefold except AttributeError: low = py23_str.lower natsort-4.0.3/test_natsort/compat/mock.py000066400000000000000000000004451254301707600205370ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import ( print_function, division, unicode_literals, absolute_import ) # Load mock functions from the right place. try: from unittest.mock import MagicMock, patch, call except ImportError: from mock import MagicMock, patch, call natsort-4.0.3/test_natsort/profile_natsorted.py000066400000000000000000000060251254301707600220460ustar00rootroot00000000000000# -*- coding: utf-8 -*- """\ This file contains functions to profile natsorted with different inputs and different settings. """ from __future__ import print_function import cProfile import random import sys sys.path.insert(0, '.') from natsort import natsorted, index_natsorted from natsort.compat.py23 import py23_range # Sample lists to sort nums = random.sample(py23_range(10000), 1000) nstr = list(map(str, random.sample(py23_range(10000), 1000))) astr = ['a'+x+'num' for x in map(str, random.sample(py23_range(10000), 1000))] tstr = [['a'+x, 'a-'+x] for x in map(str, random.sample(py23_range(10000), 1000))] cstr = ['a'+x+'-'+x for x in map(str, random.sample(py23_range(10000), 1000))] def prof_nums(a): print('*** Basic Call, Numbers ***') for _ in py23_range(1000): natsorted(a) cProfile.run('prof_nums(nums)', sort='time') def prof_num_str(a): print('*** Basic Call, Numbers as Strings ***') for _ in py23_range(1000): natsorted(a) cProfile.run('prof_num_str(nstr)', sort='time') def prof_str(a): print('*** Basic Call, Strings ***') for _ in py23_range(1000): natsorted(a) cProfile.run('prof_str(astr)', sort='time') def prof_str_index(a): print('*** Basic Index Call ***') for _ in py23_range(1000): index_natsorted(a) cProfile.run('prof_str_index(astr)', sort='time') def prof_nested(a): print('*** Basic Call, Nested Strings ***') for _ in py23_range(1000): natsorted(a) cProfile.run('prof_nested(tstr)', sort='time') def prof_str_noexp(a): print('*** No-Exp Call ***') for _ in py23_range(1000): natsorted(a, exp=False) cProfile.run('prof_str_noexp(astr)', sort='time') def prof_str_unsigned(a): print('*** Unsigned Call ***') for _ in py23_range(1000): natsorted(a, signed=False) cProfile.run('prof_str_unsigned(astr)', sort='time') def prof_str_unsigned_noexp(a): print('*** Unsigned No-Exp Call ***') for _ in py23_range(1000): natsorted(a, signed=False, exp=False) cProfile.run('prof_str_unsigned_noexp(astr)', sort='time') def prof_str_asint(a): print('*** Int Call ***') for _ in py23_range(1000): natsorted(a, number_type=int) cProfile.run('prof_str_asint(astr)', sort='time') def prof_str_asint_unsigned(a): print('*** Unsigned Int (Versions) Call ***') for _ in py23_range(1000): natsorted(a, number_type=int, signed=False) cProfile.run('prof_str_asint_unsigned(astr)', sort='time') def prof_str_key(a): print('*** Basic Call With Key ***') for _ in py23_range(1000): natsorted(a, key=lambda x: x.upper()) cProfile.run('prof_str_key(astr)', sort='time') def prof_str_index_key(a): print('*** Basic Index Call With Key ***') for _ in py23_range(1000): index_natsorted(a, key=lambda x: x.upper()) cProfile.run('prof_str_index_key(astr)', sort='time') def prof_str_unorderable(a): print('*** Basic Index Call, "Unorderable" ***') for _ in py23_range(1000): natsorted(a) cProfile.run('prof_str_unorderable(cstr)', sort='time') natsort-4.0.3/test_natsort/slow_splitters.py000066400000000000000000000126071254301707600214230ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Alternate versions of the splitting functions for testing.""" from __future__ import unicode_literals import unicodedata from natsort.compat.py23 import PY_VERSION if PY_VERSION >= 3.0: long = int def int_splitter(x, signed, safe, sep): """Alternate (slow) method to split a string into numbers.""" if not x: return [] all_digits = set('0123456789') full_list, strings, nums = [], [], [] input_len = len(x) for i, char in enumerate(x): # If this character is a sign and the next is a number, # start a new number. if (i+1 < input_len and signed and (char in '-+') and (x[i+1] in all_digits)): # Reset any current string or number. if strings: full_list.append(''.join(strings)) if nums: full_list.append(int(''.join(nums))) strings = [] nums = [char] # If this is a number, add to the number list. elif char in all_digits: nums.append(char) # Reset any string. if strings: full_list.append(''.join(strings)) strings = [] # If this is a unicode digit, append directly to the full list. elif char.isdigit(): # Reset any string or number. if strings: full_list.append(''.join(strings)) if nums: full_list.append(int(''.join(nums))) strings = [] nums = [] full_list.append(unicodedata.digit(char)) # Otherwise add to the string. else: strings.append(char) # Reset any number. if nums: full_list.append(int(''.join(nums))) nums = [] if nums: full_list.append(int(''.join(nums))) elif strings: full_list.append(''.join(strings)) if safe: full_list = sep_inserter(full_list, (int, long), sep) if type(full_list[0]) in (int, long): return [sep] + full_list else: return full_list def float_splitter(x, signed, exp, safe, sep): """Alternate (slow) method to split a string into numbers.""" if not x: return [] all_digits = set('0123456789') full_list, strings, nums = [], [], [] input_len = len(x) for i, char in enumerate(x): # If this character is a sign and the next is a number, # start a new number. if (i+1 < input_len and (signed or (i > 1 and exp and x[i-1] in 'eE' and x[i-2] in all_digits)) and (char in '-+') and (x[i+1] in all_digits)): # Reset any current string or number. if strings: full_list.append(''.join(strings)) if nums and i > 0 and x[i-1] not in 'eE': full_list.append(float(''.join(nums))) nums = [char] else: nums.append(char) strings = [] # If this is a number, add to the number list. elif char in all_digits: nums.append(char) # Reset any string. if strings: full_list.append(''.join(strings)) strings = [] # If this is a decimal, add to the number list. elif (i + 1 < input_len and char == '.' and x[i+1] in all_digits): if nums and '.' in nums: full_list.append(float(''.join(nums))) nums = [] nums.append(char) if strings: full_list.append(''.join(strings)) strings = [] # If this is an exponent, add to the number list. elif (i > 0 and i + 1 < input_len and exp and char in 'eE' and x[i-1] in all_digits and x[i+1] in all_digits | set('+-')): if 'e' in nums or 'E' in nums: strings = [char] full_list.append(float(''.join(nums))) nums = [] else: nums.append(char) # If this is a unicode digit, append directly to the full list. elif unicodedata.numeric(char, None) is not None: # Reset any string or number. if strings: full_list.append(''.join(strings)) if nums: full_list.append(float(''.join(nums))) strings = [] nums = [] full_list.append(unicodedata.numeric(char)) # Otherwise add to the string. else: strings.append(char) # Reset any number. if nums: full_list.append(float(''.join(nums))) nums = [] if nums: full_list.append(float(''.join(nums))) elif strings: full_list.append(''.join(strings)) # Fix a float that looks like a string. fstrings = ('inf', 'infinity', '-inf', '-infinity', '+inf', '+infinity', 'nan') full_list = [float(y) if type(y) != float and y.lower() in fstrings else y for y in full_list] if safe: full_list = sep_inserter(full_list, (float,), sep) if type(full_list[0]) == float: return [sep] + full_list else: return full_list def sep_inserter(x, t, sep): # Simulates the py3_safe function. ret = [x[0]] for i, y in enumerate(x[1:]): if type(y) in t and type(x[i]) in t: ret.append(sep) ret.append(y) return ret natsort-4.0.3/test_natsort/stress_natsort.py000066400000000000000000000036261254301707600214240ustar00rootroot00000000000000# -*- coding: utf-8 -*- """\ This file contains functions to stress-test natsort, looking for cases that raise an unknown exception. """ from random import randint, sample, choice from string import printable from copy import copy from pytest import fail from natsort import natsorted from natsort.compat.py23 import py23_range def test_random(): """Try to sort 100,000 randomly generated strings without exception.""" # Repeat test 100,000 times for _ in py23_range(100000): # Made a list of five randomly generated strings lst = [''.join(sample(printable, randint(7, 30))) for __ in py23_range(5)] # Try to sort. If there is an exception, give some detailed info. try: natsorted(lst) except Exception as e: msg = "Ended with exception type '{exc}: {msg}'.\n" msg += "Failed on the input {lst}." fail(msg.format(exc=type(e).__name__, msg=str(e), lst=str(lst))) def test_similar(): """Try to sort 100,000 randomly generated similar strings without exception. """ # Repeat test 100,000 times for _ in py23_range(100000): # Create a randomly generated string base = sample(printable, randint(7, 30)) # Make a list of strings based on this string, # with some randomly generated modifications lst = [] for __ in py23_range(5): new_str = copy(base) for ___ in py23_range(randint(1, 5)): new_str[randint(0, len(base)-1)] = choice(printable) lst.append(''.join(new_str)) # Try to sort. If there is an exception, give some detailed info. try: natsorted(lst) except Exception as e: msg = "Ended with exception type '{exc}: {msg}'.\n" msg += "Failed on the input {lst}." fail(msg.format(exc=type(e).__name__, msg=str(e), lst=str(lst))) natsort-4.0.3/test_natsort/test_fake_fastnumbers.py000066400000000000000000000065561254301707600227120ustar00rootroot00000000000000# -*- coding: utf-8 -*- """\ Test the fake fastnumbers module. """ from __future__ import unicode_literals import pytest import unicodedata from math import isnan from natsort.compat.py23 import py23_str from natsort.compat.fake_fastnumbers import ( fast_float, fast_int, isfloat, isint, ) from compat.hypothesis import ( assume, given, use_hypothesis, ) def is_float(x): try: float(x) except ValueError: try: unicodedata.numeric(x) except (ValueError, TypeError): return False else: return True else: return True def is_int(x): try: int(x) except ValueError: try: unicodedata.digit(x) except (ValueError, TypeError): return False else: return True else: return True # Each test has an "example" version for demonstrative purposes, # and a test that uses the hypothesis module. def test_fast_float_converts_float_string_to_float_example(): assert fast_float('45.8') == 45.8 assert fast_float('-45') == -45.0 assert fast_float('45.8e-2') == 45.8e-2 assert isnan(fast_float('nan')) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given(float) def test_fast_float_converts_float_string_to_float(x): assume(not isnan(x)) # But inf is included assert fast_float(repr(x)) == x def test_fast_float_leaves_string_as_is_example(): assert fast_float('invalid') == 'invalid' @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given(py23_str) def test_fast_float_leaves_string_as_is(x): assume(not is_float(x)) assert fast_float(x) == x def test_fast_int_leaves_float_string_as_is_example(): assert fast_int('45.8') == '45.8' assert fast_int('nan') == 'nan' assert fast_int('inf') == 'inf' @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given(float) def test_fast_int_leaves_float_string_as_is(x): assume(not x.is_integer()) assert fast_int(repr(x)) == repr(x) def test_fast_int_converts_int_string_to_int_example(): assert fast_int('-45') == -45 assert fast_int('+45') == 45 @given(int) def test_fast_int_converts_int_string_to_int(x): assert fast_int(repr(x)) == x def test_fast_int_leaves_string_as_is_example(): assert fast_int('invalid') == 'invalid' @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given(py23_str) def test_fast_int_leaves_string_as_is(x): assume(not is_int(x)) assert fast_int(x) == x def test_isfloat_returns_True_for_real_numbers_example(): assert isfloat(-45.0) assert isfloat(45.8e-2) @given(float) def test_isfloat_returns_True_for_real_numbers(x): assert isfloat(x) def test_isfloat_returns_False_for_strings_example(): assert not isfloat('45.8') assert not isfloat('invalid') @given(py23_str) def test_isfloat_returns_False_for_strings(x): assert not isfloat(x) def test_isint_returns_True_for_real_numbers_example(): assert isint(-45) assert isint(45) @given(int) def test_isint_returns_True_for_real_numbers(x): assert isint(x) def test_isint_returns_False_for_strings_example(): assert not isint('45') assert not isint('invalid') @given(py23_str) def test_isint_returns_False_for_strings(x): assert not isint(x) natsort-4.0.3/test_natsort/test_locale_help.py000066400000000000000000000110001254301707600216160ustar00rootroot00000000000000# -*- coding: utf-8 -*- """\ Test the locale help module module. """ from __future__ import unicode_literals import locale import pytest from math import isnan from itertools import chain from natsort.compat.fake_fastnumbers import fast_float, isfloat from natsort.locale_help import grouper, locale_convert from natsort.compat.py23 import py23_str from natsort.compat.locale import use_pyicu from compat.locale import ( load_locale, has_locale_de_DE, get_strxfrm, low, ) from compat.hypothesis import ( assume, given, use_hypothesis, ) # Each test has an "example" version for demonstrative purposes, # and a test that uses the hypothesis module. def test_grouper_returns_letters_with_lowercase_transform_of_letter_example(): assert grouper('HELLO', (fast_float, isfloat)) == 'hHeElLlLoO' assert grouper('hello', (fast_float, isfloat)) == 'hheelllloo' @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given(py23_str) def test_grouper_returns_letters_with_lowercase_transform_of_letter(x): assume(type(fast_float(x)) is not float) assert grouper(x, (fast_float, isfloat)) == ''.join(chain.from_iterable([low(y), y] for y in x)) def test_grouper_returns_float_string_as_float_example(): assert grouper('45.8e-2', (fast_float, isfloat)) == 45.8e-2 @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given(float) def test_grouper_returns_float_string_as_float(x): assume(not isnan(x)) assert grouper(repr(x), (fast_float, isfloat)) == x def test_locale_convert_transforms_float_string_to_float_example(): load_locale('en_US') assert locale_convert('45.8', (fast_float, isfloat), False) == 45.8 locale.setlocale(locale.LC_NUMERIC, str('')) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given(float) def test_locale_convert_transforms_float_string_to_float(x): assume(not isnan(x)) load_locale('en_US') assert locale_convert(repr(x), (fast_float, isfloat), False) == x locale.setlocale(locale.LC_NUMERIC, str('')) def test_locale_convert_transforms_nonfloat_string_to_strxfrm_string_example(): load_locale('en_US') strxfrm = get_strxfrm() assert locale_convert('45,8', (fast_float, isfloat), False) == strxfrm('45,8') assert locale_convert('hello', (fast_float, isfloat), False) == strxfrm('hello') locale.setlocale(locale.LC_NUMERIC, str('')) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given(py23_str) def test_locale_convert_transforms_nonfloat_string_to_strxfrm_string(x): assume(type(fast_float(x)) is not float) load_locale('en_US') strxfrm = get_strxfrm() assert locale_convert(x, (fast_float, isfloat), False) == strxfrm(x) locale.setlocale(locale.LC_NUMERIC, str('')) def test_locale_convert_with_groupletters_transforms_nonfloat_string_to_strxfrm_string_with_grouped_letters_example(): load_locale('en_US') strxfrm = get_strxfrm() assert locale_convert('hello', (fast_float, isfloat), True) == strxfrm('hheelllloo') assert locale_convert('45,8', (fast_float, isfloat), True) == strxfrm('4455,,88') locale.setlocale(locale.LC_NUMERIC, str('')) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given(py23_str) def test_locale_convert_with_groupletters_transforms_nonfloat_string_to_strxfrm_string_with_grouped_letters(x): assume(type(fast_float(x)) is not float) load_locale('en_US') strxfrm = get_strxfrm() assert locale_convert(x, (fast_float, isfloat), True) == strxfrm(''.join(chain.from_iterable([low(y), y] for y in x))) locale.setlocale(locale.LC_NUMERIC, str('')) @pytest.mark.skipif(not has_locale_de_DE, reason='requires de_DE locale') def test_locale_convert_transforms_float_string_to_float_with_de_locale_example(): load_locale('de_DE') assert locale_convert('45.8', (fast_float, isfloat), False) == 45.8 assert locale_convert('45,8', (fast_float, isfloat), False) == 45.8 locale.setlocale(locale.LC_NUMERIC, str('')) @pytest.mark.skipif(not has_locale_de_DE, reason='requires de_DE locale') @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given(float) def test_locale_convert_transforms_float_string_to_float_with_de_locale(x): assume(not isnan(x)) load_locale('de_DE') assert locale_convert(repr(x), (fast_float, isfloat), False) == x assert locale_convert(repr(x).replace('.', ','), (fast_float, isfloat), False) == x locale.setlocale(locale.LC_NUMERIC, str('')) natsort-4.0.3/test_natsort/test_main.py000066400000000000000000000254461254301707600203160ustar00rootroot00000000000000# -*- coding: utf-8 -*- """\ Test the natsort command-line tool functions. """ from __future__ import print_function, unicode_literals import pytest import re import sys from pytest import raises from compat.mock import patch, call from compat.hypothesis import ( assume, given, integers_from, integers_in_range, sampled_from, use_hypothesis, ) from natsort.__main__ import ( main, range_check, check_filter, keep_entry_range, exclude_entry, sort_and_print_entries, py23_str, ) def test_main_passes_default_arguments_with_no_command_line_options(): with patch('natsort.__main__.sort_and_print_entries') as p: sys.argv[1:] = ['num-2', 'num-6', 'num-1'] main() args = p.call_args[0][1] assert not args.paths assert args.filter is None assert args.reverse_filter is None assert args.exclude is None assert not args.reverse assert args.number_type == 'int' assert not args.signed assert args.exp assert not args.locale def test_main_passes_arguments_with_all_command_line_options(): with patch('natsort.__main__.sort_and_print_entries') as p: sys.argv[1:] = ['--paths', '--reverse', '--locale', '--filter', '4', '10', '--reverse-filter', '100', '110', '--number-type', 'float', '--noexp', '--sign', '--exclude', '34', '--exclude', '35', 'num-2', 'num-6', 'num-1'] main() args = p.call_args[0][1] assert args.paths assert args.filter == [(4.0, 10.0)] assert args.reverse_filter == [(100.0, 110.0)] assert args.exclude == [34, 35] assert args.reverse assert args.number_type == 'float' assert args.signed assert not args.exp assert args.locale class Args: """A dummy class to simulate the argparse Namespace object""" def __init__(self, filter, reverse_filter, exclude, as_path, reverse): self.filter = filter self.reverse_filter = reverse_filter self.exclude = exclude self.reverse = reverse self.number_type = 'float' self.signed = True self.exp = True self.paths = as_path self.locale = 0 entries = ['tmp/a57/path2', 'tmp/a23/path1', 'tmp/a1/path1', 'tmp/a1 (1)/path1', 'tmp/a130/path1', 'tmp/a64/path1', 'tmp/a64/path2'] mock_print = '__builtin__.print' if sys.version[0] == '2' else 'builtins.print' def test_sort_and_print_entries_uses_default_algorithm_with_all_options_false(): with patch(mock_print) as p: # tmp/a1 (1)/path1 # tmp/a1/path1 # tmp/a23/path1 # tmp/a57/path2 # tmp/a64/path1 # tmp/a64/path2 # tmp/a130/path1 sort_and_print_entries(entries, Args(None, None, False, False, False)) e = [call(entries[i]) for i in [3, 2, 1, 0, 5, 6, 4]] p.assert_has_calls(e) def test_sort_and_print_entries_uses_PATH_algorithm_with_path_option_true_to_properly_sort_OS_generated_path_names(): with patch(mock_print) as p: # tmp/a1/path1 # tmp/a1 (1)/path1 # tmp/a23/path1 # tmp/a57/path2 # tmp/a64/path1 # tmp/a64/path2 # tmp/a130/path1 sort_and_print_entries(entries, Args(None, None, False, True, False)) e = [call(entries[i]) for i in [2, 3, 1, 0, 5, 6, 4]] p.assert_has_calls(e) def test_sort_and_print_entries_keeps_only_paths_between_of_20_to_100_with_filter_option(): with patch(mock_print) as p: # tmp/a23/path1 # tmp/a57/path2 # tmp/a64/path1 # tmp/a64/path2 sort_and_print_entries(entries, Args([(20, 100)], None, False, False, False)) e = [call(entries[i]) for i in [1, 0, 5, 6]] p.assert_has_calls(e) def test_sort_and_print_entries_excludes_paths_between_of_20_to_100_with_reverse_filter_option(): with patch(mock_print) as p: # tmp/a1/path1 # tmp/a1 (1)/path1 # tmp/a130/path1 sort_and_print_entries(entries, Args(None, [(20, 100)], False, True, False)) e = [call(entries[i]) for i in [2, 3, 4]] p.assert_has_calls(e) def test_sort_and_print_entries_excludes_paths_23_or_130_with_exclude_option_list(): with patch(mock_print) as p: # tmp/a1/path1 # tmp/a1 (1)/path1 # tmp/a57/path2 # tmp/a64/path1 # tmp/a64/path2 sort_and_print_entries(entries, Args(None, None, [23, 130], True, False)) e = [call(entries[i]) for i in [2, 3, 0, 5, 6]] p.assert_has_calls(e) def test_sort_and_print_entries_reverses_order_with_reverse_option(): with patch(mock_print) as p: # tmp/a130/path1 # tmp/a64/path2 # tmp/a64/path1 # tmp/a57/path2 # tmp/a23/path1 # tmp/a1 (1)/path1 # tmp/a1/path1 sort_and_print_entries(entries, Args(None, None, False, True, True)) e = [call(entries[i]) for i in reversed([2, 3, 1, 0, 5, 6, 4])] p.assert_has_calls(e) # Each test has an "example" version for demonstrative purposes, # and a test that uses the hypothesis module. def test_range_check_returns_range_as_is_but_with_floats_if_first_is_less_than_second_example(): assert range_check(10, 11) == (10.0, 11.0) assert range_check(6.4, 30) == (6.4, 30.0) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given(x=int, y=int) def test_range_check_returns_range_as_is_but_with_floats_if_first_is_less_than_second(x, y): assume(x < y) assert range_check(x, y) == (float(x), float(y)) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given(x=float, y=float) def test_range_check_returns_range_as_is_but_with_floats_if_first_is_less_than_second2(x, y): assume(x < y) assert range_check(x, y) == (x, y) def test_range_check_raises_ValueError_if_second_is_less_than_first_example(): with raises(ValueError) as err: range_check(7, 2) assert str(err.value) == 'low >= high' @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given(x=float, y=float) def test_range_check_raises_ValueError_if_second_is_less_than_first(x, y): assume(x >= y) with raises(ValueError) as err: range_check(x, x) assert str(err.value) == 'low >= high' def test_check_filter_returns_None_if_filter_evaluates_to_False(): assert check_filter(()) is None assert check_filter(False) is None assert check_filter(None) is None def test_check_filter_converts_filter_numbers_to_floats_if_filter_is_valid_example(): assert check_filter([(6, 7)]) == [(6.0, 7.0)] assert check_filter([(6, 7), (2, 8)]) == [(6.0, 7.0), (2.0, 8.0)] @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given(x=(int, int, float, float), y=(int, float, float, int)) def test_check_filter_converts_filter_numbers_to_floats_if_filter_is_valid(x, y): assume(all(i < j for i, j in zip(x, y))) assert check_filter(list(zip(x, y))) == [(float(i), float(j)) for i, j in zip(x, y)] def test_check_filter_raises_ValueError_if_filter_is_invalid_example(): with raises(ValueError) as err: check_filter([(7, 2)]) assert str(err.value) == 'Error in --filter: low >= high' @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given(x=(int, int, float, float), y=(int, float, float, int)) def test_check_filter_raises_ValueError_if_filter_is_invalid(x, y): assume(any(i >= j for i, j in zip(x, y))) with raises(ValueError) as err: check_filter(list(zip(x, y))) assert str(err.value) == 'Error in --filter: low >= high' def test_keep_entry_range_returns_True_if_any_portion_of_input_is_between_the_range_bounds_example(): assert keep_entry_range('a56b23c89', [0], [100], int, re.compile(r'\d+')) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given((py23_str, integers_in_range(1, 99), py23_str, integers_in_range(1, 99), py23_str)) def test_keep_entry_range_returns_True_if_any_portion_of_input_is_between_the_range_bounds(x): s = ''.join(map(py23_str, x)) assume(any(0 < int(i) < 100 for i in re.findall(r'\d+', s) if re.match(r'\d+$', i))) assert keep_entry_range(s, [0], [100], int, re.compile(r'\d+')) def test_keep_entry_range_returns_True_if_any_portion_of_input_is_between_any_range_bounds_example(): assert keep_entry_range('a56b23c89', [1, 88], [20, 90], int, re.compile(r'\d+')) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given((py23_str, integers_in_range(2, 89), py23_str, integers_in_range(2, 89), py23_str)) def test_keep_entry_range_returns_True_if_any_portion_of_input_is_between_any_range_bounds(x): s = ''.join(map(py23_str, x)) assume(any((1 < int(i) < 20) or (88 < int(i) < 90) for i in re.findall(r'\d+', s) if re.match(r'\d+$', i))) assert keep_entry_range(s, [1, 88], [20, 90], int, re.compile(r'\d+')) def test_keep_entry_range_returns_False_if_no_portion_of_input_is_between_the_range_bounds_example(): assert not keep_entry_range('a56b23c89', [1], [20], int, re.compile(r'\d+')) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given((py23_str, integers_from(21), py23_str, integers_from(21), py23_str)) def test_keep_entry_range_returns_False_if_no_portion_of_input_is_between_the_range_bounds(x): s = ''.join(map(py23_str, x)) assume(all(not (1 <= int(i) <= 20) for i in re.findall(r'\d+', s) if re.match(r'\d+$', i))) assert not keep_entry_range(s, [1], [20], int, re.compile(r'\d+')) def test_exclude_entry_returns_True_if_exlcude_parameters_are_not_in_input_example(): assert exclude_entry('a56b23c89', [100, 45], int, re.compile(r'\d+')) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given((py23_str, integers_from(0), py23_str, integers_from(0), py23_str)) def test_exclude_entry_returns_True_if_exlcude_parameters_are_not_in_input(x): s = ''.join(map(py23_str, x)) assume(not any(int(i) in (23, 45, 87) for i in re.findall(r'\d+', s) if re.match(r'\d+$', i))) assert exclude_entry(s, [23, 45, 87], int, re.compile(r'\d+')) def test_exclude_entry_returns_False_if_exlcude_parameters_are_in_input_example(): assert not exclude_entry('a56b23c89', [23], int, re.compile(r'\d+')) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given((py23_str, sampled_from([23, 45, 87]), py23_str, sampled_from([23, 45, 87]), py23_str)) def test_exclude_entry_returns_False_if_exlcude_parameters_are_in_input(x): s = ''.join(map(py23_str, x)) assume(any(int(i) in (23, 45, 87) for i in re.findall(r'\d+', s) if re.match(r'\d+$', i))) assert not exclude_entry(s, [23, 45, 87], int, re.compile(r'\d+')) natsort-4.0.3/test_natsort/test_natsort.py000066400000000000000000000376421254301707600210650ustar00rootroot00000000000000# -*- coding: utf-8 -*- """\ Here are a collection of examples of how this module can be used. See the README or the natsort homepage for more details. """ from __future__ import unicode_literals, print_function import pytest import sys import warnings import locale from operator import itemgetter from pytest import raises from natsort import ( natsorted, index_natsorted, natsort_key, versorted, index_versorted, humansorted, index_humansorted, natsort_keygen, order_by_index, ns, realsorted, index_realsorted, decoder, as_ascii, as_utf8, ) from compat.locale import load_locale, has_locale_de_DE from natsort.utils import _natsort_key def test_decoder_returns_function_that_can_decode_bytes_but_return_non_bytes_as_is(): f = decoder('latin1') a = 'bytes' b = 14 assert f(b'bytes') == a assert f(b) is b # returns as-is, same object ID if sys.version[0] == '3': assert f(a) is a # same object returned on Python3 b/c only bytes has decode else: assert f(a) is not a assert f(a) == a # not same object on Python2 because str can decode def test_as_ascii_returns_bytes_as_ascii(): assert decoder('ascii')(b'bytes') == as_ascii(b'bytes') def test_as_utf8_returns_bytes_as_utf8(): assert decoder('utf8')(b'bytes') == as_utf8(b'bytes') def test_natsort_key_public_raises_DeprecationWarning_when_called(): # Identical to _natsort_key # But it raises a deprecation warning with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") assert natsort_key('a-5.034e2') == _natsort_key('a-5.034e2', key=None, alg=ns.I) assert len(w) == 1 assert "natsort_key is deprecated as of 3.4.0, please use natsort_keygen" in str(w[-1].message) # It is called for each element in a list when sorting with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") a = ['a2', 'a5', 'a9', 'a1', 'a4', 'a10', 'a6'] a.sort(key=natsort_key) assert len(w) == 7 def test_natsort_keygen_returns_natsort_key_with_alg_option(): a = 'a-5.034e1' assert natsort_keygen()(a) == _natsort_key(a, None, ns.I) assert natsort_keygen(alg=ns.F | ns.S)(a) == _natsort_key(a, None, ns.F | ns.S) def test_natsort_keygen_with_key_returns_same_result_as_nested_lambda_with_bare_natsort_key(): a = 'a-5.034e1' f1 = natsort_keygen(key=lambda x: x.upper()) def f2(x): return _natsort_key(x, lambda y: y.upper(), ns.I) assert f1(a) == f2(a) def test_natsort_keygen_returns_key_that_can_be_used_to_sort_list_in_place_with_same_result_as_natsorted(): a = ['a50', 'a51.', 'a50.31', 'a50.4', 'a5.034e1', 'a50.300'] b = a[:] a.sort(key=natsort_keygen(alg=ns.F)) assert a == natsorted(b, alg=ns.F) def test_natsorted_returns_strings_with_numbers_in_ascending_order(): a = ['a2', 'a5', 'a9', 'a1', 'a4', 'a10', 'a6'] assert natsorted(a) == ['a1', 'a2', 'a4', 'a5', 'a6', 'a9', 'a10'] def test_natsorted_returns_list_of_numbers_sorted_as_signed_floats_with_exponents(): a = ['a50', 'a51.', 'a50.31', 'a-50', 'a50.4', 'a5.034e1', 'a50.300'] assert natsorted(a, alg=ns.REAL) == ['a-50', 'a50', 'a50.300', 'a50.31', 'a5.034e1', 'a50.4', 'a51.'] def test_natsorted_returns_list_of_numbers_sorted_as_unsigned_floats_without_exponents_with_NOEXP_option(): a = ['a50', 'a51.', 'a50.31', 'a-50', 'a50.4', 'a5.034e1', 'a50.300'] assert natsorted(a, alg=ns.N | ns.F | ns.U) == ['a5.034e1', 'a50', 'a50.300', 'a50.31', 'a50.4', 'a51.', 'a-50'] # UNSIGNED is default assert natsorted(a, alg=ns.NOEXP | ns.FLOAT) == ['a5.034e1', 'a50', 'a50.300', 'a50.31', 'a50.4', 'a51.', 'a-50'] def test_natsorted_returns_list_of_numbers_sorted_as_unsigned_ints_with_INT_option(): a = ['a50', 'a51.', 'a50.31', 'a-50', 'a50.4', 'a5.034e1', 'a50.300'] assert natsorted(a, alg=ns.INT) == ['a5.034e1', 'a50', 'a50.4', 'a50.31', 'a50.300', 'a51.', 'a-50'] # INT is default assert natsorted(a) == ['a5.034e1', 'a50', 'a50.4', 'a50.31', 'a50.300', 'a51.', 'a-50'] def test_natsorted_returns_list_of_numbers_sorted_as_unsigned_ints_with_DIGIT_and_VERSION_option(): a = ['a50', 'a51.', 'a50.31', 'a-50', 'a50.4', 'a5.034e1', 'a50.300'] assert natsorted(a, alg=ns.DIGIT) == ['a5.034e1', 'a50', 'a50.4', 'a50.31', 'a50.300', 'a51.', 'a-50'] assert natsorted(a, alg=ns.VERSION) == ['a5.034e1', 'a50', 'a50.4', 'a50.31', 'a50.300', 'a51.', 'a-50'] def test_natsorted_returns_list_of_numbers_sorted_as_signed_ints_with_SIGNED_option(): a = ['a50', 'a51.', 'a50.31', 'a-50', 'a50.4', 'a5.034e1', 'a50.300'] assert natsorted(a, alg=ns.SIGNED) == ['a-50', 'a5.034e1', 'a50', 'a50.4', 'a50.31', 'a50.300', 'a51.'] def test_natsorted_returns_list_of_numbers_sorted_accounting_for_sign_with_SIGNED_option(): a = ['a-5', 'a7', 'a+2'] assert natsorted(a, alg=ns.SIGNED) == ['a-5', 'a+2', 'a7'] def test_natsorted_returns_list_of_numbers_sorted_not_accounting_for_sign_without_SIGNED_option(): a = ['a-5', 'a7', 'a+2'] assert natsorted(a) == ['a7', 'a+2', 'a-5'] def test_natsorted_returns_sorted_list_of_version_numbers_by_default_or_with_VERSION_option(): a = ['1.9.9a', '1.11', '1.9.9b', '1.11.4', '1.10.1'] assert natsorted(a) == ['1.9.9a', '1.9.9b', '1.10.1', '1.11', '1.11.4'] assert natsorted(a, alg=ns.VERSION) == ['1.9.9a', '1.9.9b', '1.10.1', '1.11', '1.11.4'] def test_natsorted_returns_sorted_list_with_mixed_type_input_and_does_not_raise_TypeError_on_Python3(): # You can mix types with natsorted. This can get around the new # 'unorderable types' issue with Python 3. a = [6, 4.5, '7', '2.5', 'a'] assert natsorted(a) == ['2.5', 4.5, 6, '7', 'a'] a = [46, '5a5b2', 'af5', '5a5-4'] assert natsorted(a) == ['5a5-4', '5a5b2', 46, 'af5'] def test_natsorted_with_mixed_input_returns_sorted_results_without_error(): a = ['2', 'ä', 'b', 1.5, 3] assert natsorted(a) == [1.5, '2', 3, 'b', 'ä'] def test_natsorted_with_nan_input_returns_sorted_results_with_nan_last_with_NANLAST(): a = ['25', 5, float('nan'), 1E40] # The slice is because NaN != NaN assert natsorted(a, alg=ns.NANLAST)[:3] == [5, '25', 1E40, float('nan')][:3] def test_natsorted_with_nan_input_returns_sorted_results_with_nan_first_without_NANLAST(): a = ['25', 5, float('nan'), 1E40] # The slice is because NaN != NaN assert natsorted(a)[1:] == [float('nan'), 5, '25', 1E40][1:] def test_natsorted_with_mixed_input_raises_TypeError_if_bytes_type_is_involved_on_Python3(): if sys.version[0] == '3': with raises(TypeError) as e: assert natsorted(['ä', b'b']) assert 'bytes' in str(e.value) else: assert True def test_natsorted_raises_ValueError_for_non_iterable_input(): with raises(TypeError) as err: natsorted(100) assert str(err.value) == "'int' object is not iterable" def test_natsorted_recursivley_applies_key_to_nested_lists_to_return_sorted_nested_list(): data = [['a1', 'a5'], ['a1', 'a40'], ['a10', 'a1'], ['a2', 'a5']] assert natsorted(data) == [['a1', 'a5'], ['a1', 'a40'], ['a2', 'a5'], ['a10', 'a1']] def test_natsorted_applies_key_to_each_list_element_before_sorting_list(): b = [('a', 'num3'), ('b', 'num5'), ('c', 'num2')] assert natsorted(b, key=itemgetter(1)) == [('c', 'num2'), ('a', 'num3'), ('b', 'num5')] def test_natsorted_returns_list_in_reversed_order_with_reverse_option(): a = ['a50', 'a51.', 'a50.31', 'a50.4', 'a5.034e1', 'a50.300'] assert natsorted(a, reverse=True) == natsorted(a)[::-1] def test_natsorted_sorts_OS_generated_paths_incorrectly_without_PATH_option(): a = ['/p/Folder (10)/file.tar.gz', '/p/Folder/file.tar.gz', '/p/Folder (1)/file (1).tar.gz', '/p/Folder (1)/file.tar.gz'] assert natsorted(a) == ['/p/Folder (1)/file (1).tar.gz', '/p/Folder (1)/file.tar.gz', '/p/Folder (10)/file.tar.gz', '/p/Folder/file.tar.gz'] def test_natsorted_sorts_OS_generated_paths_correctly_with_PATH_option(): a = ['/p/Folder (10)/file.tar.gz', '/p/Folder/file.tar.gz', '/p/Folder (1)/file (1).tar.gz', '/p/Folder (1)/file.tar.gz'] assert natsorted(a, alg=ns.PATH) == ['/p/Folder/file.tar.gz', '/p/Folder (1)/file.tar.gz', '/p/Folder (1)/file (1).tar.gz', '/p/Folder (10)/file.tar.gz'] def test_natsorted_can_handle_sorting_paths_and_numbers_with_PATH(): # You can sort paths and numbers, not that you'd want to a = ['/Folder (9)/file.exe', 43] assert natsorted(a, alg=ns.PATH) == [43, '/Folder (9)/file.exe'] def test_natsorted_returns_results_in_ASCII_order_with_no_case_options(): a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana'] assert natsorted(a) == ['Apple', 'Banana', 'Corn', 'apple', 'banana', 'corn'] def test_natsorted_returns_results_sorted_by_lowercase_ASCII_order_with_IGNORECASE(): a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana'] assert natsorted(a, alg=ns.IGNORECASE) == ['Apple', 'apple', 'Banana', 'banana', 'corn', 'Corn'] def test_natsorted_returns_results_in_ASCII_order_but_with_lowercase_letters_first_with_LOWERCASEFIRST(): a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana'] assert natsorted(a, alg=ns.LOWERCASEFIRST) == ['apple', 'banana', 'corn', 'Apple', 'Banana', 'Corn'] def test_natsorted_returns_results_with_uppercase_and_lowercase_letters_grouped_together_with_GROUPLETTERS(): a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana'] assert natsorted(a, alg=ns.GROUPLETTERS) == ['Apple', 'apple', 'Banana', 'banana', 'Corn', 'corn'] def test_natsorted_returns_results_in_natural_order_with_GROUPLETTERS_and_LOWERCASEFIRST(): a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana'] assert natsorted(a, alg=ns.G | ns.LF) == ['apple', 'Apple', 'banana', 'Banana', 'corn', 'Corn'] def test_natsorted_places_uppercase_letters_before_lowercase_letters_for_nested_input(): b = [('A5', 'a6'), ('a3', 'a1')] assert natsorted(b) == [('A5', 'a6'), ('a3', 'a1')] def test_natsorted_with_LOWERCASEFIRST_places_lowercase_letters_before_uppercase_letters_for_nested_input(): b = [('A5', 'a6'), ('a3', 'a1')] assert natsorted(b, alg=ns.LOWERCASEFIRST) == [('a3', 'a1'), ('A5', 'a6')] def test_natsorted_with_IGNORECASE_sorts_without_regard_to_case_for_nested_input(): b = [('A5', 'a6'), ('a3', 'a1')] assert natsorted(b, alg=ns.IGNORECASE) == [('a3', 'a1'), ('A5', 'a6')] def test_natsorted_with_LOCALE_returns_results_sorted_by_lowercase_first_and_grouped_letters(): a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana'] load_locale('en_US') assert natsorted(a, alg=ns.LOCALE) == ['apple', 'Apple', 'banana', 'Banana', 'corn', 'Corn'] locale.setlocale(locale.LC_ALL, str('')) def test_natsorted_with_LOCALE_and_CAPITALFIRST_returns_results_sorted_by_capital_first_and_ungrouped(): a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana'] load_locale('en_US') assert natsorted(a, alg=ns.LOCALE | ns.CAPITALFIRST) == ['Apple', 'Banana', 'Corn', 'apple', 'banana', 'corn'] locale.setlocale(locale.LC_ALL, str('')) def test_natsorted_with_LOCALE_and_LOWERCASEFIRST_returns_results_sorted_by_uppercase_first_and_grouped_letters(): a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana'] load_locale('en_US') assert natsorted(a, alg=ns.LOCALE | ns.LOWERCASEFIRST) == ['Apple', 'apple', 'Banana', 'banana', 'Corn', 'corn'] locale.setlocale(locale.LC_ALL, str('')) def test_natsorted_with_LOCALE_and_CAPITALFIRST_and_LOWERCASE_returns_results_sorted_by_capital_last_and_ungrouped(): a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana'] load_locale('en_US') assert natsorted(a, alg=ns.LOCALE | ns.CAPITALFIRST | ns.LOWERCASEFIRST) == ['apple', 'banana', 'corn', 'Apple', 'Banana', 'Corn'] locale.setlocale(locale.LC_ALL, str('')) def test_natsorted_with_LOCALE_and_en_setting_returns_results_sorted_by_en_language(): load_locale('en_US') a = ['c', 'ä', 'b', 'a5,6', 'a5,50'] assert natsorted(a, alg=ns.LOCALE | ns.F) == ['a5,6', 'a5,50', 'ä', 'b', 'c'] locale.setlocale(locale.LC_ALL, str('')) @pytest.mark.skipif(not has_locale_de_DE, reason='requires de_DE locale') def test_natsorted_with_LOCALE_and_de_setting_returns_results_sorted_by_de_language(): load_locale('de_DE') a = ['c', 'ä', 'b', 'a5,6', 'a5,50'] assert natsorted(a, alg=ns.LOCALE | ns.F) == ['a5,50', 'a5,6', 'ä', 'b', 'c'] locale.setlocale(locale.LC_ALL, str('')) def test_natsorted_with_LOCALE_and_mixed_input_returns_sorted_results_without_error(): load_locale('en_US') a = ['0', 'Á', '2', 'Z'] assert natsorted(a) == ['0', '2', 'Z', 'Á'] a = ['2', 'ä', 'b', 1.5, 3] assert natsorted(a, alg=ns.LOCALE) == [1.5, '2', 3, 'ä', 'b'] locale.setlocale(locale.LC_ALL, str('')) def test_versorted_returns_results_identical_to_natsorted(): a = ['1.9.9a', '1.11', '1.9.9b', '1.11.4', '1.10.1'] # versorted is retained for backwards compatibility assert versorted(a) == natsorted(a) def test_realsorted_returns_results_identical_to_natsorted_with_REAL(): a = ['a50', 'a51.', 'a50.31', 'a-50', 'a50.4', 'a5.034e1', 'a50.300'] assert realsorted(a) == natsorted(a, alg=ns.REAL) def test_humansorted_returns_results_identical_to_natsorted_with_LOCALE(): a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana'] assert humansorted(a) == natsorted(a, alg=ns.LOCALE) def test_index_natsorted_returns_integer_list_of_sort_order_for_input_list(): a = ['num3', 'num5', 'num2'] b = ['foo', 'bar', 'baz'] index = index_natsorted(a) assert index == [2, 0, 1] assert [a[i] for i in index] == ['num2', 'num3', 'num5'] assert [b[i] for i in index] == ['baz', 'foo', 'bar'] def test_index_natsorted_returns_reversed_integer_list_of_sort_order_for_input_list_with_reverse_option(): a = ['num3', 'num5', 'num2'] assert index_natsorted(a, reverse=True) == [1, 0, 2] def test_index_natsorted_applies_key_function_before_sorting(): c = [('a', 'num3'), ('b', 'num5'), ('c', 'num2')] assert index_natsorted(c, key=itemgetter(1)) == [2, 0, 1] def test_index_natsorted_handles_unorderable_types_error_on_Python3(): a = [46, '5a5b2', 'af5', '5a5-4'] assert index_natsorted(a) == [3, 1, 0, 2] def test_index_natsorted_returns_integer_list_of_nested_input_list(): data = [['a1', 'a5'], ['a1', 'a40'], ['a10', 'a1'], ['a2', 'a5']] assert index_natsorted(data) == [0, 1, 3, 2] def test_index_natsorted_returns_integer_list_in_proper_order_for_input_paths_with_PATH(): a = ['/p/Folder (10)/', '/p/Folder/', '/p/Folder (1)/'] assert index_natsorted(a, alg=ns.PATH) == [1, 2, 0] def test_index_versorted_returns_results_identical_to_index_natsorted(): a = ['1.9.9a', '1.11', '1.9.9b', '1.11.4', '1.10.1'] # index_versorted is retained for backwards compatibility assert index_versorted(a) == index_natsorted(a) def test_index_realsorted_returns_results_identical_to_index_natsorted_with_REAL(): a = ['a50', 'a51.', 'a50.31', 'a-50', 'a50.4', 'a5.034e1', 'a50.300'] assert index_realsorted(a) == index_natsorted(a, alg=ns.REAL) def test_index_humansorted_returns_results_identical_to_index_natsorted_with_LOCALE(): a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana'] assert index_humansorted(a) == index_natsorted(a, alg=ns.LOCALE) def test_order_by_index_sorts_list_according_to_order_of_integer_list(): a = ['num3', 'num5', 'num2'] index = [2, 0, 1] assert order_by_index(a, index) == ['num2', 'num3', 'num5'] assert order_by_index(a, index) == [a[i] for i in index] def test_order_by_index_returns_generator_with_iter_True(): a = ['num3', 'num5', 'num2'] index = [2, 0, 1] assert order_by_index(a, index, True) != [a[i] for i in index] assert list(order_by_index(a, index, True)) == [a[i] for i in index] natsort-4.0.3/test_natsort/test_unicode_numbers.py000066400000000000000000000026171254301707600225460ustar00rootroot00000000000000# -*- coding: utf-8 -*- """\ Test the Unicode numbers module. """ from __future__ import unicode_literals import unicodedata from natsort.compat.py23 import py23_range, py23_unichr from natsort.unicode_numbers import ( numeric_chars, numeric, digit_chars, digits, ) def test_numeric_chars_contains_only_valid_unicode_numeric_characters(): for a in numeric_chars: assert unicodedata.numeric(a, None) is not None def test_digit_chars_contains_only_valid_unicode_digit_characters(): for a in digit_chars: assert unicodedata.digit(a, None) is not None def test_numeric_chars_contains_all_valid_unicode_numeric_characters(): for i in py23_range(0X10FFFF): try: a = py23_unichr(i) except ValueError: break if a in set('0123456789'): continue if unicodedata.numeric(a, None) is not None: assert a in numeric_chars def test_digit_chars_contains_all_valid_unicode_digit_characters(): for i in py23_range(0X10FFFF): try: a = py23_unichr(i) except ValueError: break if a in set('0123456789'): continue if unicodedata.digit(a, None) is not None: assert a in digit_chars def test_combined_string_contains_all_characters_in_list(): assert numeric == ''.join(numeric_chars) assert digits == ''.join(digit_chars) natsort-4.0.3/test_natsort/test_utils.py000066400000000000000000001023131254301707600205170ustar00rootroot00000000000000# -*- coding: utf-8 -*- """These test the utils.py functions.""" from __future__ import unicode_literals import sys import locale import pathlib import pytest import string from math import isnan from operator import itemgetter from itertools import chain from pytest import raises from natsort.ns_enum import ns from natsort.utils import ( _number_extracter, _py3_safe, _natsort_key, _args_to_enum, _float_sign_exp_re, _float_nosign_exp_re, _float_sign_noexp_re, _float_nosign_noexp_re, _int_nosign_re, _int_sign_re, _do_decoding, _path_splitter, _fix_nan, ) from natsort.locale_help import locale_convert from natsort.compat.py23 import py23_str from natsort.compat.locale import ( use_pyicu, null_string, dumb_sort, ) from natsort.compat.fastnumbers import ( fast_float, fast_int, isint, ) from slow_splitters import ( int_splitter, float_splitter, sep_inserter, ) from compat.locale import ( load_locale, get_strxfrm, low, ) from compat.hypothesis import ( assume, given, example, sampled_from, use_hypothesis, ) if sys.version[0] == '3': long = int ichain = chain.from_iterable def test_do_decoding_decodes_bytes_string_to_unicode(): assert type(_do_decoding(b'bytes', 'ascii')) is py23_str assert _do_decoding(b'bytes', 'ascii') == 'bytes' assert _do_decoding(b'bytes', 'ascii') == b'bytes'.decode('ascii') def test_args_to_enum_raises_TypeError_for_invalid_argument(): with raises(TypeError): _args_to_enum(**{'alf': 0}) def test_args_to_enum_converts_signed_exp_float_to_ns_F(): # number_type, signed, exp, as_path, py3_safe assert _args_to_enum(**{'number_type': float, 'signed': True, 'exp': True}) == ns.F | ns.S def test_args_to_enum_converts_signed_noexp_float_to_ns_FN(): # number_type, signed, exp, as_path, py3_safe assert _args_to_enum(**{'number_type': float, 'signed': True, 'exp': False}) == ns.F | ns.N | ns.S def test_args_to_enum_converts_unsigned_exp_float_to_ns_FU(): # number_type, signed, exp, as_path, py3_safe assert _args_to_enum(**{'number_type': float, 'signed': False, 'exp': True}) == ns.F | ns.U # unsigned is default assert _args_to_enum(**{'number_type': float, 'signed': False, 'exp': True}) == ns.F def test_args_to_enum_converts_unsigned_unexp_float_to_ns_FNU(): # number_type, signed, exp, as_path, py3_safe assert _args_to_enum(**{'number_type': float, 'signed': False, 'exp': False}) == ns.F | ns.U | ns.N def test_args_to_enum_converts_float_and_path_and_py3safe_to_ns_FPT(): # number_type, signed, exp, as_path, py3_safe assert _args_to_enum(**{'number_type': float, 'as_path': True, 'py3_safe': True}) == ns.F | ns.P | ns.T def test_args_to_enum_converts_int_and_path_to_ns_IP(): # number_type, signed, exp, as_path, py3_safe assert _args_to_enum(**{'number_type': int, 'as_path': True}) == ns.I | ns.P def test_args_to_enum_converts_unsigned_int_and_py3safe_to_ns_IUT(): # number_type, signed, exp, as_path, py3_safe assert _args_to_enum(**{'number_type': int, 'signed': False, 'py3_safe': True}) == ns.I | ns.U | ns.T def test_args_to_enum_converts_None_to_ns_IU(): # number_type, signed, exp, as_path, py3_safe assert _args_to_enum(**{'number_type': None, 'exp': True}) == ns.I | ns.U float_nosafe_locale_group = (fast_float, False, True, True) float_nosafe_locale_nogroup = (fast_float, False, True, False) float_safe_nolocale_nogroup = (fast_float, True, False, False) float_nosafe_nolocale_group = (fast_float, False, False, True) float_nosafe_nolocale_nogroup = (fast_float, False, False, False) int_safe_locale_group = (fast_int, True, True, True) int_safe_locale_nogroup = (fast_int, True, True, False) int_safe_nolocale_group = (fast_int, True, False, True) int_safe_nolocale_nogroup = (fast_int, True, False, False) int_nosafe_locale_group = (fast_int, False, True, True) int_nosafe_locale_nogroup = (fast_int, False, True, False) int_nosafe_nolocale_group = (fast_int, False, False, True) int_nosafe_nolocale_nogroup = (fast_int, False, False, False) def test_fix_nan_converts_nan_to_negative_infinity_without_NANLAST(): assert _fix_nan((float('nan'),), 0) == (float('-inf'),) assert _fix_nan(('a', 'b', float('nan')), 0) == ('a', 'b', float('-inf')) def test_fix_nan_converts_nan_to_positive_infinity_with_NANLAST(): assert _fix_nan((float('nan'),), ns.NANLAST) == (float('+inf'),) assert _fix_nan(('a', 'b', float('nan')), ns.NANLAST) == ('a', 'b', float('+inf')) # Each test has an "example" version for demonstrative purposes, # and a test that uses the hypothesis module. def test_py3_safe_does_nothing_if_no_numbers_example(): assert _py3_safe(['a', 'b', 'c'], False, isint) == ['a', 'b', 'c'] assert _py3_safe(['a'], False, isint) == ['a'] def test_py3_safe_does_nothing_if_only_one_number_example(): assert _py3_safe(['a', 5], False, isint) == ['a', 5] def test_py3_safe_inserts_empty_string_between_two_numbers_example(): assert _py3_safe([5, 9], False, isint) == [5, '', 9] def test_py3_safe_with_use_locale_inserts_null_string_between_two_numbers_example(): assert _py3_safe([5, 9], True, isint) == [5, null_string, 9] @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([py23_str, int]) def test_py3_safe_inserts_empty_string_between_two_numbers(x): assume(bool(x)) assert _py3_safe(x, False, isint) == sep_inserter(x, (int, long), '') def test_path_splitter_splits_path_string_by_separator_example(): z = '/this/is/a/path' assert _path_splitter(z) == list(pathlib.Path(z).parts) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([sampled_from(string.ascii_letters)]) def test_path_splitter_splits_path_string_by_separator(x): assume(len(x) > 1) assume(all(x)) z = py23_str(pathlib.Path(*x)) assert _path_splitter(z) == list(pathlib.Path(z).parts) def test_path_splitter_splits_path_string_by_separator_and_removes_extension_example(): z = '/this/is/a/path/file.exe' y = list(pathlib.Path(z).parts) assert _path_splitter(z) == y[:-1] + [pathlib.Path(z).stem] + [pathlib.Path(z).suffix] @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([sampled_from(string.ascii_letters)]) def test_path_splitter_splits_path_string_by_separator_and_removes_extension(x): assume(len(x) > 2) assume(all(x)) z = py23_str(pathlib.Path(*x[:-2])) + '.' + x[-1] y = list(pathlib.Path(z).parts) assert _path_splitter(z) == y[:-1] + [pathlib.Path(z).stem] + [pathlib.Path(z).suffix] def test_number_extracter_raises_TypeError_if_given_a_number_example(): with raises(TypeError): assert _number_extracter(50.0, _float_sign_exp_re, *float_nosafe_nolocale_nogroup) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given(float) def test_number_extracter_raises_TypeError_if_given_a_number(x): with raises(TypeError): assert _number_extracter(x, _float_sign_exp_re, *float_nosafe_nolocale_nogroup) def test_number_extracter_includes_plus_sign_and_exponent_in_float_definition_for_signed_exp_floats_example(): assert _number_extracter('a5+5.034e-1', _float_sign_exp_re, *float_nosafe_nolocale_nogroup) == ['a', 5.0, 0.5034] @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test_number_extracter_includes_plus_sign_and_exponent_in_float_definition_for_signed_exp_floats(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert _number_extracter(s, _float_sign_exp_re, *float_nosafe_nolocale_nogroup) == float_splitter(s, True, True, False, '') def test_number_extracter_excludes_plus_sign_in_float_definition_but_includes_exponent_for_unsigned_exp_floats_example(): assert _number_extracter('a5+5.034e-1', _float_nosign_exp_re, *float_nosafe_nolocale_nogroup) == ['a', 5.0, '+', 0.5034] @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test_number_extracter_excludes_plus_sign_in_float_definition_but_includes_exponent_for_unsigned_exp_floats(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert _number_extracter(s, _float_nosign_exp_re, *float_nosafe_nolocale_nogroup) == float_splitter(s, False, True, False, '') def test_number_extracter_includes_plus_and_minus_sign_in_float_definition_but_excludes_exponent_for_signed_noexp_floats_example(): assert _number_extracter('a5+5.034e-1', _float_sign_noexp_re, *float_nosafe_nolocale_nogroup) == ['a', 5.0, 5.034, 'e', -1.0] @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test_number_extracter_includes_plus_and_minus_sign_in_float_definition_but_excludes_exponent_for_signed_noexp_floats(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert _number_extracter(s, _float_sign_noexp_re, *float_nosafe_nolocale_nogroup) == float_splitter(s, True, False, False, '') def test_number_extracter_excludes_plus_sign_and_exponent_in_float_definition_for_unsigned_noexp_floats_example(): assert _number_extracter('a5+5.034e-1', _float_nosign_noexp_re, *float_nosafe_nolocale_nogroup) == ['a', 5.0, '+', 5.034, 'e-', 1.0] @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test_number_extracter_excludes_plus_sign_and_exponent_in_float_definition_for_unsigned_noexp_floats(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert _number_extracter(s, _float_nosign_noexp_re, *float_nosafe_nolocale_nogroup) == float_splitter(s, False, False, False, '') def test_number_extracter_excludes_plus_and_minus_sign_in_int_definition_for_unsigned_ints_example(): assert _number_extracter('a5+5.034e-1', _int_nosign_re, *int_nosafe_nolocale_nogroup) == ['a', 5, '+', 5, '.', 34, 'e-', 1] @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) @example([10000000000000000000000000000000000000000000000000000000000000000000000000, 100000000000000000000000000000000000000000000000000000000000000000000000000, 100000000000000000000000000000000000000000000000000000000000000000000000000]) def test_number_extracter_excludes_plus_and_minus_sign_in_int_definition_for_unsigned_ints(x): assume(len(x) <= 10) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert _number_extracter(s, _int_nosign_re, *int_nosafe_nolocale_nogroup) == int_splitter(s, False, False, '') def test_number_extracter_includes_plus_and_minus_sign_in_int_definition_for_signed_ints_example(): assert _number_extracter('a5+5.034e-1', _int_sign_re, *int_nosafe_nolocale_nogroup) == ['a', 5, 5, '.', 34, 'e', -1] @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test_number_extracter_includes_plus_and_minus_sign_in_int_definition_for_signed_ints(x): assume(len(x) <= 10) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert _number_extracter(s, _int_sign_re, *int_nosafe_nolocale_nogroup) == int_splitter(s, True, False, '') def test_number_extracter_inserts_empty_string_between_floats_for_py3safe_option_example(): assert _number_extracter('a5+5.034e-1', _float_sign_exp_re, *float_safe_nolocale_nogroup) == ['a', 5.0, '', 0.5034] @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test_number_extracter_inserts_empty_string_between_floats_for_py3safe_option(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert _number_extracter(s, _float_sign_exp_re, *float_safe_nolocale_nogroup) == float_splitter(s, True, True, True, '') def test_number_extracter_inserts_empty_string_between_ints_for_py3safe_option_example(): assert _number_extracter('a5+5.034e-1', _int_sign_re, *int_safe_nolocale_nogroup) == ['a', 5, '', 5, '.', 34, 'e', -1] @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test_number_extracter_inserts_empty_string_between_ints_for_py3safe_option(x): assume(len(x) <= 10) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert _number_extracter(s, _int_sign_re, *int_safe_nolocale_nogroup) == int_splitter(s, True, True, '') def test_number_extracter_inserts_no_empty_string_py3safe_option_because_no_numbers_are_adjascent_example(): assert _number_extracter('a5+5.034e-1', _float_nosign_exp_re, *float_safe_nolocale_nogroup) == ['a', 5.0, '+', 0.5034] def test_number_extracter_adds_leading_empty_string_if_input_begins_with_a_number_example(): assert _number_extracter('6a5+5.034e-1', _float_sign_exp_re, *float_nosafe_nolocale_nogroup) == ['', 6.0, 'a', 5.0, 0.5034] def test_number_extracter_adds_leading_empty_string_if_input_begins_with_a_number_and_empty_string_between_numbers_for_py3safe_exmple(): assert _number_extracter('6a5+5.034e-1', _float_sign_exp_re, *float_safe_nolocale_nogroup) == ['', 6.0, 'a', 5.0, '', 0.5034] def test_number_extracter_doubles_letters_with_lowercase_version_with_groupletters_for_float_example(): assert _number_extracter('A5+5.034E-1', _float_sign_exp_re, *float_nosafe_nolocale_group) == ['aA', 5.0, 0.5034] @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test_number_extracter_doubles_letters_with_lowercase_version_with_groupletters_for_float(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) t = float_splitter(s, True, True, False, '') t = [''.join([low(z) + z for z in y]) if type(y) != float else y for y in t] assert _number_extracter(s, _float_sign_exp_re, *float_nosafe_nolocale_group) == t def test_number_extracter_doubles_letters_with_lowercase_version_with_groupletters_for_int_example(): assert _number_extracter('A5+5.034E-1', _int_nosign_re, *int_nosafe_nolocale_group) == ['aA', 5, '++', 5, '..', 34, 'eE--', 1] @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test_number_extracter_doubles_letters_with_lowercase_version_with_groupletters_for_int(x): assume(len(x) <= 10) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) t = int_splitter(s, False, False, '') t = [''.join([low(z) + z for z in y]) if type(y) not in (int, long) else y for y in t] assert _number_extracter(s, _int_nosign_re, *int_nosafe_nolocale_group) == t def test_number_extracter_extracts_numbers_and_strxfrms_strings_with_use_locale_example(): load_locale('en_US') strxfrm = get_strxfrm() assert _number_extracter('A5+5.034E-1', _int_nosign_re, *int_nosafe_locale_nogroup) == [strxfrm('A'), 5, strxfrm('+'), 5, strxfrm('.'), 34, strxfrm('E-'), 1] locale.setlocale(locale.LC_NUMERIC, str('')) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test_number_extracter_extracts_numbers_and_strxfrms_strings_with_use_locale(x): assume(len(x) <= 10) load_locale('en_US') s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) t = int_splitter(s, False, False, null_string) try: # Account for locale bug on Python 3.2 t = [y if i == 0 and y is null_string else locale_convert(y, (fast_int, isint), False) for i, y in enumerate(t)] assert _number_extracter(s, _int_nosign_re, *int_nosafe_locale_nogroup) == t except OverflowError: pass locale.setlocale(locale.LC_NUMERIC, str('')) def test_number_extracter_extracts_numbers_and_strxfrms_letter_doubled_strings_with_use_locale_and_groupletters_example(): load_locale('en_US') strxfrm = get_strxfrm() assert _number_extracter('A5+5.034E-1', _int_nosign_re, *int_nosafe_locale_group) == [strxfrm('aA'), 5, strxfrm('++'), 5, strxfrm('..'), 34, strxfrm('eE--'), 1] locale.setlocale(locale.LC_NUMERIC, str('')) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test_number_extracter_extracts_numbers_and_strxfrms_letter_doubled_strings_with_use_locale_and_groupletters(x): assume(len(x) <= 10) load_locale('en_US') s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) t = int_splitter(s, False, False, null_string) try: # Account for locale bug on Python 3.2 t = [y if i == 0 and y is null_string else locale_convert(y, (fast_int, isint), True) for i, y in enumerate(t)] assert _number_extracter(s, _int_nosign_re, *int_nosafe_locale_group) == t except OverflowError: pass locale.setlocale(locale.LC_NUMERIC, str('')) def test__natsort_key_with_nan_input_transforms_nan_to_negative_inf(): assert _natsort_key('nan', None, ns.FLOAT) == ('', float('-inf')) assert _natsort_key(float('nan'), None, 0) == ('', float('-inf')) def test__natsort_key_with_nan_input_and_NANLAST_transforms_nan_to_positive_inf(): assert _natsort_key('nan', None, ns.FLOAT | ns.NANLAST) == ('', float('+inf')) assert _natsort_key(float('nan'), None, ns.NANLAST) == ('', float('+inf')) assert ns.NL == ns.NANLAST # The remaining tests provide no examples, just hypothesis tests. # They only confirm that _natsort_key uses the above building blocks. @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_float_and_signed_splits_input_into_string_and_signed_float_with_exponent(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert ns.F == ns.FLOAT assert ns.S == ns.SIGNED assert _natsort_key(s, None, ns.F | ns.S) == tuple(_number_extracter(s, _float_sign_exp_re, *float_nosafe_nolocale_nogroup)) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_real_splits_input_into_string_and_signed_float_with_exponent(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert ns.R == ns.F | ns.S assert _natsort_key(s, None, ns.R) == tuple(_number_extracter(s, _float_sign_exp_re, *float_nosafe_nolocale_nogroup)) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_real_matches_signed_float(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert _natsort_key(s, None, ns.R) == _natsort_key(s, None, ns.F | ns.S) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_float_and_signed_and_noexp_splits_input_into_string_and_signed_float_without_exponent(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert ns.N == ns.NOEXP assert _natsort_key(s, None, ns.F | ns.S | ns.N) == tuple(_number_extracter(s, _float_sign_noexp_re, *float_nosafe_nolocale_nogroup)) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_float_and_unsigned_splits_input_into_string_and_unsigned_float(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert ns.U == ns.UNSIGNED assert _natsort_key(s, None, ns.F | ns.U) == tuple(_number_extracter(s, _float_nosign_exp_re, *float_nosafe_nolocale_nogroup)) # Default is unsigned search assert _natsort_key(s, None, ns.F) == tuple(_number_extracter(s, _float_nosign_exp_re, *float_nosafe_nolocale_nogroup)) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_float_and_noexp_splits_input_into_string_and_unsigned_float_without_exponent(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert _natsort_key(s, None, ns.F | ns.N) == tuple(_number_extracter(s, _float_nosign_noexp_re, *float_nosafe_nolocale_nogroup)) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_int_splits_input_into_string_and_unsigned_int(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert ns.I == ns.INT assert _natsort_key(s, None, ns.INT) == tuple(_number_extracter(s, _int_nosign_re, *int_nosafe_nolocale_nogroup)) # Default is int search assert _natsort_key(s, None, ns.NOEXP) == tuple(_number_extracter(s, _int_nosign_re, *int_nosafe_nolocale_nogroup)) # NOEXP is ignored for integers assert _natsort_key(s, None, ns.I | ns.NOEXP) == tuple(_number_extracter(s, _int_nosign_re, *int_nosafe_nolocale_nogroup)) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_int_splits_and_signed_input_into_string_and_signed_int(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert _natsort_key(s, None, ns.INT | ns.SIGNED) == tuple(_number_extracter(s, _int_sign_re, *int_nosafe_nolocale_nogroup)) assert _natsort_key(s, None, ns.SIGNED) == tuple(_number_extracter(s, _int_sign_re, *int_nosafe_nolocale_nogroup)) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_version_or_digit_matches_usigned_int(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert _natsort_key(s, None, ns.VERSION) == _natsort_key(s, None, ns.INT | ns.UNSIGNED) assert _natsort_key(s, None, ns.DIGIT) == _natsort_key(s, None, ns.VERSION) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_key_applies_key_function_before_splitting(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert _natsort_key(s, lambda x: x.upper(), ns.I) == tuple(_number_extracter(s.upper(), _int_nosign_re, *int_nosafe_nolocale_nogroup)) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_tuple_input_returns_nested_tuples(x): # Iterables are parsed recursively so you can sort lists of lists. assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) t = tuple(_number_extracter(s, _int_nosign_re, *int_nosafe_nolocale_nogroup)) assert _natsort_key((s, s), None, ns.I) == (t, t) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_tuple_input_but_itemgetter_key_returns_split_second_element(x): # A key is applied before recursion, but not in the recursive calls. assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) t = tuple(_number_extracter(s, _int_nosign_re, *int_nosafe_nolocale_nogroup)) assert _natsort_key((s, s), itemgetter(1), ns.I) == t @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given(float) def test__natsort_key_with_numeric_input_returns_number_with_leading_empty_string(x): assume(not isnan(x)) if x.is_integer(): x = int(x) assert _natsort_key(x, None, ns.I) == ('', x) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_TYPESAFE_inserts_spaces_between_numbers(x): # Turn on TYPESAFE to put a '' between adjacent numbers assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert _natsort_key(s, None, ns.TYPESAFE | ns.S) == tuple(_number_extracter(s, _int_sign_re, *int_safe_nolocale_nogroup)) def test__natsort_key_with_invalid_alg_input_raises_ValueError(): # Invalid arguments give the correct response with raises(ValueError) as err: _natsort_key('a', None, '1') assert str(err.value) == "_natsort_key: 'alg' argument must be from the enum 'ns', got 1" @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_IGNORECASE_lowercases_text(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) try: assert _natsort_key(s, None, ns.IGNORECASE) == tuple(_number_extracter(s.casefold(), _int_nosign_re, *int_nosafe_nolocale_nogroup)) except AttributeError: assert _natsort_key(s, None, ns.IGNORECASE) == tuple(_number_extracter(s.lower(), _int_nosign_re, *int_nosafe_nolocale_nogroup)) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_LOWERCASEFIRST_inverts_text_case(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert _natsort_key(s, None, ns.LOWERCASEFIRST) == tuple(_number_extracter(s.swapcase(), _int_nosign_re, *int_nosafe_nolocale_nogroup)) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_GROUPLETTERS_doubles_text_with_lowercase_letter_first(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(ichain([repr(y)] if type(y) in (float, long, int) else [low(y), y] for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) t = _number_extracter(s, _int_nosign_re, *int_nosafe_nolocale_nogroup) assert _natsort_key(s, None, ns.GROUPLETTERS) == tuple(''.join(low(z) + z for z in y) if type(y) not in (float, long, int) else y for y in t) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_GROUPLETTERS_and_LOWERCASEFIRST_inverts_text_first_then_doubles_letters_with_lowercase_letter_first(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(ichain([repr(y)] if type(y) in (float, long, int) else [low(y), y] for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) t = _number_extracter(s.swapcase(), _int_nosign_re, *int_nosafe_nolocale_nogroup) assert _natsort_key(s, None, ns.G | ns.LF) == tuple(''.join(low(z) + z for z in y) if type(y) not in (float, long, int) else y for y in t) def test__natsort_key_with_bytes_input_only_applies_LOWERCASEFIRST_or_IGNORECASE_and_returns_in_tuple(): if sys.version[0] == '3': assert _natsort_key(b'Apple56', None, ns.I) == (b'Apple56',) assert _natsort_key(b'Apple56', None, ns.LF) == (b'aPPLE56',) assert _natsort_key(b'Apple56', None, ns.IC) == (b'apple56',) assert _natsort_key(b'Apple56', None, ns.G) == (b'Apple56',) else: assert True @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_LOCALE_transforms_floats_according_to_the_current_locale_and_strxfrms_strings(x): # Locale aware sorting assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) load_locale('en_US') if dumb_sort(): assert _natsort_key(s, None, ns.LOCALE | ns.F) == tuple(_number_extracter(s.swapcase(), _float_nosign_exp_re, *float_nosafe_locale_group)) else: assert _natsort_key(s, None, ns.LOCALE | ns.F) == tuple(_number_extracter(s, _float_nosign_exp_re, *float_nosafe_locale_nogroup)) locale.setlocale(locale.LC_NUMERIC, str('')) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_LOCALE_and_UNGROUPLETTERS_places_space_before_string_with_capital_first_letter(x): # Locale aware sorting assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) load_locale('en_US') if dumb_sort(): t = tuple(_number_extracter(s.swapcase(), _float_nosign_exp_re, *float_nosafe_locale_group)) else: t = tuple(_number_extracter(s, _float_nosign_exp_re, *float_nosafe_locale_nogroup)) if not t: r = (t, t) elif t[0] is null_string: r = ((b'' if use_pyicu else '',), t) else: r = ((s[0],), t) assert _natsort_key(s, None, ns.LOCALE | ns.UNGROUPLETTERS | ns.F) == r # The below are all aliases for UNGROUPLETTERS assert ns.UNGROUPLETTERS == ns.UG assert ns.UNGROUPLETTERS == ns.CAPITALFIRST assert ns.UNGROUPLETTERS == ns.C locale.setlocale(locale.LC_NUMERIC, str('')) @pytest.mark.skipif(not use_hypothesis, reason='requires python2.7 or greater') @given([float, py23_str, int]) def test__natsort_key_with_UNGROUPLETTERS_does_nothing_without_LOCALE(x): assume(len(x) <= 10) assume(not any(type(y) == float and isnan(y) for y in x)) s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x) assert _natsort_key(s, None, ns.UG | ns.I) == _natsort_key(s, None, ns.I) # It is difficult to generate code that will create random filesystem paths, # so "example" based tests are given for the PATH option. def test__natsort_key_with_absolute_path_intput_and_PATH_returns_nested_tuple_where_each_element_is_path_component_with_leading_root_and_split_extensions(): # Turn on PATH to split a file path into components assert _natsort_key('/p/Folder (10)/file34.5nm (2).tar.gz', None, ns.PATH | ns.F) == (('/',), ('p', ), ('Folder (', 10.0, ')',), ('file', 34.5, 'nm (', 2.0, ')'), ('.tar',), ('.gz',)) def test__natsort_key_with_relative_path_intput_and_PATH_returns_nested_tuple_where_each_element_is_path_component_with_leading_relative_parent_and_split_extensions(): assert _natsort_key('../Folder (10)/file (2).tar.gz', None, ns.PATH | ns.F) == (('..', ), ('Folder (', 10.0, ')',), ('file (', 2.0, ')'), ('.tar',), ('.gz',)) def test__natsort_key_with_relative_path_intput_and_PATH_returns_nested_tuple_where_each_element_is_path_component_and_split_extensions(): assert _natsort_key('Folder (10)/file.f34.5nm (2).tar.gz', None, ns.PATH | ns.F) == (('Folder (', 10.0, ')',), ('file.f', 34.5, 'nm (', 2.0, ')'), ('.tar',), ('.gz',)) def test__natsort_key_with_pathlib_intput_and_PATH_returns_nested_tuples(): # Converts pathlib PurePath (and subclass) objects to string before sorting assert _natsort_key(pathlib.Path('../Folder (10)/file (2).tar.gz'), None, ns.PATH | ns.F) == (('..', ), ('Folder (', 10.0, ')',), ('file (', 2.0, ')'), ('.tar',), ('.gz',)) def test__natsort_key_with_numeric_input_and_PATH_returns_number_in_nested_tuple(): # It gracefully handles as_path for numeric input by putting an extra tuple around it # so it will sort against the other as_path results. assert _natsort_key(10, None, ns.PATH) == (('', 10),) def test__natsort_key_with_tuple_of_paths_and_PATH_returns_triply_nested_tuple(): # PATH also handles recursion well. assert _natsort_key(('/Folder', '/Folder (1)'), None, ns.PATH) == ((('/',), ('Folder',)), (('/',), ('Folder (', 1, ')'))) natsort-4.0.3/tox.ini000066400000000000000000000005501254301707600145300ustar00rootroot00000000000000# Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py26, py27, py32, py33, py34, pypy [testenv] commands = {envpython} setup.py test deps = pytest