Codebase list natsort / upstream/5.4.1
New upstream version 5.4.1 Agustin Henze 5 years ago
50 changed file(s) with 4466 addition(s) and 3148 deletion(s). Raw diff Collapse all Expand all
2828 .pytest_cache
2929 .pytest
3030 .envrc
31 .venv
3132
3233 #Translations
3334 *.mo
00 language: python
1 matrix:
1
2 jobs:
23 include:
34 - python: "2.7"
45 dist: trusty
4041 dist: xenial
4142 sudo: true
4243 env: WITH_EXTRAS=""
44 - stage: code-quality
45 python: "3.6"
46 dist: trusty
47 sudo: false
48 install: pip install flake8 flake8-import-order flake8-bugbear pep8-naming
49 script: flake8
4350
4451 install:
4552 - pip install -U pip
4855 script:
4956 - tox
5057
58 stages:
59 - code-quality
60 - test
61
5162 after_success:
5263 - coverage xml
5364 - python-codacy-coverage -r coverage.xml
00 [dev-packages]
1 coverage = "*"
2 pytest = "*"
3 pytest-cov = "*"
4 pytest-flakes = "*"
5 pytest-pep8 = "*"
6 hypothesis = ">=3.8.0"
7 astroid = "==1.5.3"
1 coverage = "*"
2 pytest = ">=3.5"
3 pytest-cov = "*"
4 pytest-mock = ">=1.1"
5 hypothesis = ">=3.8.0"
86 pytest-faulthandler = {version = "*", platform_python_implementation = "== 'CPython'"}
97
108 # These packages are standard on newer python versions.
119 pathlib = {version = "*", python_version = "< '3.4'"}
12 mock = {version = "*", python_version = "< '3.3'"}
2727 - `Examples and Recipes <http://natsort.readthedocs.io/en/master/examples.html>`_
2828 - `How Does Natsort Work? <http://natsort.readthedocs.io/en/master/howitworks.html>`_
2929 - `API <http://natsort.readthedocs.io/en/master/api.html>`_
30 - **NOTE**: The old documentation at pythonhosted.org has been taken down
31 with no redirects. Please see
32 `this post <https://opensource.stackexchange.com/q/5941/8999>`_ for an
33 explanation into why.
3430
3531 - `FAQ`_
3632 - `Optional Dependencies`_
335331
336332 The most efficient sorting can occur if you install the
337333 `fastnumbers <https://pypi.org/project/fastnumbers>`_ package
338 (version >=0.7.1); it helps with the string to number conversions.
334 (version >=2.0.0); it helps with the string to number conversions.
339335 ``natsort`` will still run (efficiently) without the package, but if you need
340336 to squeeze out that extra juice it is recommended you include this as a dependency.
341337 ``natsort`` will not require (or check) that
99 :maxdepth: 2
1010
1111 natsort_keygen.rst
12 natsort_key.rst
1213 natsorted.rst
1314 versorted.rst
1415 humansorted.rst
11
22 Changelog
33 ---------
4
5 09-09-2018 v. 5.4.1
6 +++++++++++++++++++
7
8 - Fix error in a newly added test.
9 - Changed code format and quality checking infrastructure.
10
11 09-06-2018 v. 5.4.0
12 +++++++++++++++++++
13
14 - Re-expose ``natsort_key`` as "public" and remove the
15 associated ``DepricationWarning``.
16 - Add better developer documentation.
17 - Refactor tests.
18 - Bump allowed ``fastnumbers`` version.
419
520 07-07-2018 v. 5.3.3
621 +++++++++++++++++++
1616 # If extensions (or modules to document with autodoc) are in another directory,
1717 # add these directories to sys.path here. If the directory is relative to the
1818 # documentation root, use os.path.abspath to make it absolute, like shown here.
19 #sys.path.insert(0, os.path.abspath('.'))
19 # sys.path.insert(0, os.path.abspath('.'))
2020
2121 # -- General configuration ------------------------------------------------
2222
2323 # If your documentation needs a minimal Sphinx version, state it here.
24 #needs_sphinx = '1.0'
24 # needs_sphinx = '1.0'
2525
2626 # Add any Sphinx extension module names here, as strings. They can be
2727 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
4141 source_suffix = '.rst'
4242
4343 # The encoding of source files.
44 #source_encoding = 'utf-8-sig'
44 # source_encoding = 'utf-8-sig'
4545
4646 # The master toctree document.
4747 master_doc = 'index'
4848
4949 # General information about the project.
5050 project = u'natsort'
51 # noinspection PyShadowingBuiltins
5152 copyright = u'2014, Seth M. Morton'
5253
5354 # The version info for the project you're documenting, acts as replacement for
5556 # built documents.
5657 #
5758 # The full version, including alpha/beta/rc tags.
58 release = '5.3.3'
59 release = '5.4.1'
5960 # The short X.Y version.
6061 version = '.'.join(release.split('.')[0:2])
6162
6263 # The language for content autogenerated by Sphinx. Refer to documentation
6364 # for a list of supported languages.
64 #language = None
65 # language = None
6566
6667 # There are two options for replacing |today|: either, you set today to some
6768 # non-false value, then it is used:
68 #today = ''
69 # today = ''
6970 # Else, today_fmt is used as the format for a strftime call.
70 #today_fmt = '%B %d, %Y'
71 # today_fmt = '%B %d, %Y'
7172
7273 # List of patterns, relative to source directory, that match files and
7374 # directories to ignore when looking for source files.
7576
7677 # The reST default role (used for this markup: `text`) to use for all
7778 # documents.
78 #default_role = None
79 # default_role = None
7980
8081 # If true, '()' will be appended to :func: etc. cross-reference text.
81 #add_function_parentheses = True
82 # add_function_parentheses = True
8283
8384 # If true, the current module name will be prepended to all description
8485 # unit titles (such as .. function::).
85 #add_module_names = True
86 # add_module_names = True
8687
8788 # If true, sectionauthor and moduleauthor directives will be shown in the
8889 # output. They are ignored by default.
89 #show_authors = False
90 # show_authors = False
9091
9192 # The name of the Pygments (syntax highlighting) style to use.
9293 pygments_style = 'sphinx'
9394 highlight_language = 'python'
9495
9596 # A list of ignored prefixes for module index sorting.
96 #modindex_common_prefix = []
97 # modindex_common_prefix = []
9798
9899 # If true, keep warnings as "system message" paragraphs in the built documents.
99 #keep_warnings = False
100 # keep_warnings = False
100101
101102
102103 # -- Options for HTML output ----------------------------------------------
108109 html_theme = 'default'
109110 else:
110111 import sphinx_rtd_theme
112
111113 html_theme = 'sphinx_rtd_theme'
112114 # html_theme = 'solar'
113115
114116 # Theme options are theme-specific and customize the look and feel of a theme
115117 # further. For a list of options available for each theme, see the
116118 # documentation.
117 #html_theme_options = {}
119 # html_theme_options = {}
118120
119121 # Add any paths that contain custom themes here, relative to this directory.
120122 html_theme_path = ['.']
121123
122124 # The name for this set of Sphinx documents. If None, it defaults to
123125 # "<project> v<release> documentation".
124 #html_title = None
126 # html_title = None
125127
126128 # A shorter title for the navigation bar. Default is the same as html_title.
127 #html_short_title = None
129 # html_short_title = None
128130
129131 # The name of an image file (relative to this directory) to place at the top
130132 # of the sidebar.
131 #html_logo = None
133 # html_logo = None
132134
133135 # The name of an image file (within the static path) to use as favicon of the
134136 # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
135137 # pixels large.
136 #html_favicon = None
138 # html_favicon = None
137139
138140 # Add any paths that contain custom static files (such as style sheets) here,
139141 # relative to this directory. They are copied after the builtin static files,
143145 # Add any extra paths that contain custom files (such as robots.txt or
144146 # .htaccess) here, relative to this directory. These files are copied
145147 # directly to the root of the documentation.
146 #html_extra_path = []
148 # html_extra_path = []
147149
148150 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
149151 # using the given strftime format.
150 #html_last_updated_fmt = '%b %d, %Y'
152 # html_last_updated_fmt = '%b %d, %Y'
151153
152154 # If true, SmartyPants will be used to convert quotes and dashes to
153155 # typographically correct entities.
154 #html_use_smartypants = True
156 # html_use_smartypants = True
155157
156158 # Custom sidebar templates, maps document names to template names.
157 #html_sidebars = {}
159 # html_sidebars = {}
158160
159161 # Additional templates that should be rendered to pages, maps page names to
160162 # template names.
161 #html_additional_pages = {}
163 # html_additional_pages = {}
162164
163165 # If false, no module index is generated.
164 #html_domain_indices = True
166 # html_domain_indices = True
165167
166168 # If false, no index is generated.
167 #html_use_index = True
169 # html_use_index = True
168170
169171 # If true, the index is split into individual pages for each letter.
170 #html_split_index = False
172 # html_split_index = False
171173
172174 # If true, links to the reST sources are added to the pages.
173 #html_show_sourcelink = True
175 # html_show_sourcelink = True
174176
175177 # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
176 #html_show_sphinx = True
178 # html_show_sphinx = True
177179
178180 # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
179 #html_show_copyright = True
181 # html_show_copyright = True
180182
181183 # If true, an OpenSearch description file will be output, and all pages will
182184 # contain a <link> tag referring to it. The value of this option must be the
183185 # base URL from which the finished HTML is served.
184 #html_use_opensearch = ''
186 # html_use_opensearch = ''
185187
186188 # This is the file name suffix for HTML files (e.g. ".xhtml").
187 #html_file_suffix = None
189 # html_file_suffix = None
188190
189191 # Output file base name for HTML help builder.
190192 htmlhelp_basename = 'natsortdoc'
191193
192
193194 # -- Options for LaTeX output ---------------------------------------------
194195
195196 latex_elements = {
196 # The paper size ('letterpaper' or 'a4paper').
197 #'papersize': 'letterpaper',
198
199 # The font size ('10pt', '11pt' or '12pt').
200 #'pointsize': '10pt',
201
202 # Additional stuff for the LaTeX preamble.
203 #'preamble': '',
197 # The paper size ('letterpaper' or 'a4paper').
198 # 'papersize': 'letterpaper',
199
200 # The font size ('10pt', '11pt' or '12pt').
201 # 'pointsize': '10pt',
202
203 # Additional stuff for the LaTeX preamble.
204 # 'preamble': '',
204205 }
205206
206207 # Grouping the document tree into LaTeX files. List of tuples
207208 # (source start file, target name, title,
208209 # author, documentclass [howto, manual, or own class]).
209210 latex_documents = [
210 ('index', 'natsort.tex', u'natsort Documentation',
211 u'Seth M. Morton', 'manual'),
211 ('index', 'natsort.tex', u'natsort Documentation',
212 u'Seth M. Morton', 'manual'),
212213 ]
213214
214215 # The name of an image file (relative to this directory) to place at the top of
215216 # the title page.
216 #latex_logo = None
217 # latex_logo = None
217218
218219 # For "manual" documents, if this is true, then toplevel headings are parts,
219220 # not chapters.
220 #latex_use_parts = False
221 # latex_use_parts = False
221222
222223 # If true, show page references after internal links.
223 #latex_show_pagerefs = False
224 # latex_show_pagerefs = False
224225
225226 # If true, show URL addresses after external links.
226 #latex_show_urls = False
227 # latex_show_urls = False
227228
228229 # Documents to append as an appendix to all manuals.
229 #latex_appendices = []
230 # latex_appendices = []
230231
231232 # If false, no module index is generated.
232 #latex_domain_indices = True
233 # latex_domain_indices = True
233234
234235
235236 # -- Options for manual page output ---------------------------------------
242243 ]
243244
244245 # If true, show URL addresses after external links.
245 #man_show_urls = False
246 # man_show_urls = False
246247
247248
248249 # -- Options for Texinfo output -------------------------------------------
251252 # (source start file, target name, title, author,
252253 # dir menu entry, description, category)
253254 texinfo_documents = [
254 ('index', 'natsort', u'natsort Documentation',
255 u'Seth M. Morton', 'natsort', 'One line description of project.',
256 'Miscellaneous'),
255 ('index', 'natsort', u'natsort Documentation',
256 u'Seth M. Morton', 'natsort', 'One line description of project.',
257 'Miscellaneous'),
257258 ]
258259
259260 # Documents to append as an appendix to all manuals.
260 #texinfo_appendices = []
261 # texinfo_appendices = []
261262
262263 # If false, no module index is generated.
263 #texinfo_domain_indices = True
264 # texinfo_domain_indices = True
264265
265266 # How to display URL addresses: 'footnote', 'no', or 'inline'.
266 #texinfo_show_urls = 'footnote'
267 # texinfo_show_urls = 'footnote'
267268
268269 # If true, do not generate a @detailmenu in the "Top" node's menu.
269 #texinfo_no_detailmenu = False
270 # texinfo_no_detailmenu = False
270271
271272
272273 # Example configuration for intersphinx: refer to the Python standard library.
145145 Starting with :mod:`natsort` version 4.0.0 the default number definition was
146146 changed to an *unsigned integer* which satisfies the "least astonishment" principle, and
147147 I have not heard a complaint since.
148
149 .. admonition:: Wouldn't itertools.groupby work as well as regex to split strings?
150
151 You *could* do it using something like :func:`itertools.groupby`, but it is not clearer
152 nor more concise, *I promise*.
153
154 .. code-block:: python
155
156 >>> import itertools
157 >>> import operator
158 >>> list(map(''.join, map(operator.itemgetter(1), itertools.groupby('2 ft 11 in', str.isdigit))))
159 ['2', ' ft ', '11', ' in']
160
161 OK, but let's assume for a moment that you *really* like itertools and think the above
162 is fine. We still have lost a lot of flexibility here because of the :meth:`str.isdigit`
163 call which makes this method non-optimal; with a regular expression one can change
164 the pattern string and split on much more complicated patterns, but with
165 :func:`itertools.groupby` it becomes *much* more complicated to change it up;
166 I implemented this strategy `as part of my testing`_ and it is anything but clear an concise.
167
168 Not to mention it's *way* slower than regex. Just the simple example above (unsigned integers)
169 is 50% slower than regex...
170148
171149 Coercing Strings Containing Numbers Into Numbers
172150 ++++++++++++++++++++++++++++++++++++++++++++++++
494472
495473 .. code-block:: python
496474
497 >>> from natsort.utils import _sep_inserter
498 >>> list(_sep_inserter(iter(['apples']), ''))
475 >>> from natsort.utils import sep_inserter
476 >>> list(sep_inserter(iter(['apples']), ''))
499477 ['apples']
500478 >>>
501 >>> list(_sep_inserter(iter([12, ' apples']), ''))
479 >>> list(sep_inserter(iter([12, ' apples']), ''))
502480 ['', 12, ' apples']
503481 >>>
504 >>> list(_sep_inserter(iter(['version', 5, -3]), ''))
482 >>> list(sep_inserter(iter(['version', 5, -3]), ''))
505483 ['version', 5, '', -3]
506484 >>>
507485 >>> from natsort import natsort_keygen, ns
612590 ... coerced_input = (coerce_to_float(s) for s in split_input)
613591 ... else:
614592 ... coerced_input = (coerce_to_int(s) for s in split_input)
615 ... return tuple(_sep_inserter(coerced_input, ''))
593 ... return tuple(sep_inserter(coerced_input, ''))
616594 ...
617595
618596 And this doesn't even show handling :class:`bytes` type! Notice that we have
10911069 .. [#f3]
10921070 I'm not going to show how this is implemented in this document,
10931071 but if you are interested you can look at the code to
1094 :func:`_sep_inserter` in `util.py`_.
1072 :func:`sep_inserter` in `util.py`_.
10951073 .. [#f4]
10961074 Handling each of these is straightforward, but coupled with the rapidly
10971075 fracturing execution paths presented in :ref:`TL;DR 2 <tldr2>` one can imagine
329329
330330 The most efficient sorting can occur if you install the
331331 `fastnumbers <https://pypi.org/project/fastnumbers>`_ package
332 (version >=0.7.1); it helps with the string to number conversions.
332 (version >=2.0.0); it helps with the string to number conversions.
333333 :mod:`natsort` will still run (efficiently) without the package, but if you need
334334 to squeeze out that extra juice it is recommended you include this as a dependency.
335335 :mod:`natsort` will not require (or check) that
6868
6969 .. _bug_note:
7070
71 `locale <https://docs.python.org/3.5/library/locale.html>`_ Is Broken on Mac OS X
72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
71 The `locale <https://docs.python.org/3.5/library/locale.html>`_ Module Is Broken on Mac OS X
72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7373
7474 It's not Python's fault, but the OS... the locale library for BSD-based systems
7575 (of which Mac OS X is one) is broken. See the following links:
0 .. default-domain:: py
1 .. currentmodule:: natsort
2
3 :func:`~natsort.natsort_key`
4 ============================
5
6 .. autofunction:: natsort_key
7
143143 ./folder (1)/file.txt
144144 ./folder (2)/file.txt
145145 ./folder (10)/file.txt
146
00 # -*- coding: utf-8 -*-
1 from __future__ import (
2 print_function,
3 division,
4 unicode_literals,
5 absolute_import
6 )
1 from __future__ import absolute_import, division, print_function, unicode_literals
72
8 # Local imports.
93 import sys
104
11 from natsort.utils import chain_functions
12 from natsort._version import __version__
13
145 from natsort.natsort import (
6 as_ascii,
7 as_utf8,
8 decoder,
9 humansorted,
10 index_humansorted,
11 index_natsorted,
12 index_realsorted,
13 index_versorted,
1514 natsort_key,
1615 natsort_keygen,
1716 natsorted,
17 ns,
18 order_by_index,
19 realsorted,
1820 versorted,
19 humansorted,
20 realsorted,
21 index_natsorted,
22 index_versorted,
23 index_humansorted,
24 index_realsorted,
25 order_by_index,
26 decoder,
27 as_ascii,
28 as_utf8,
29 ns,
3021 )
22 from natsort.utils import chain_functions
3123
3224 if float(sys.version[:3]) < 3:
3325 from natsort.natsort import natcmp
3426
27 __version__ = "5.4.1"
28
3529 __all__ = [
36 'natsort_key',
37 'natsort_keygen',
38 'natsorted',
39 'versorted',
40 'humansorted',
41 'realsorted',
42 'index_natsorted',
43 'index_versorted',
44 'index_humansorted',
45 'index_realsorted',
46 'order_by_index',
47 'decoder',
48 'natcmp',
49 'as_ascii',
50 'as_utf8',
51 'ns',
52 'chain_functions',
30 "natsort_key",
31 "natsort_keygen",
32 "natsorted",
33 "versorted",
34 "humansorted",
35 "realsorted",
36 "index_natsorted",
37 "index_versorted",
38 "index_humansorted",
39 "index_realsorted",
40 "order_by_index",
41 "decoder",
42 "natcmp",
43 "as_ascii",
44 "as_utf8",
45 "ns",
46 "chain_functions",
5347 ]
5448
5549 # Add the ns keys to this namespace for convenience.
56 globals().update(
57 dict((k, v) for k, v in vars(ns).items() if not k.startswith('_'))
58 )
50 # A dict comprehension is not used for Python 2.6 compatibility.
51 globals().update(dict((k, getattr(ns, k)) for k in dir(ns) if k.isupper()))
00 # -*- coding: utf-8 -*-
1 from __future__ import (
2 print_function,
3 division,
4 unicode_literals,
5 absolute_import
6 )
7
8 # Std. lib imports.
1 from __future__ import absolute_import, division, print_function, unicode_literals
2
93 import sys
104
11 # Local imports.
12 from natsort.natsort import natsorted, ns
13 from natsort.utils import _regex_chooser
14 from natsort._version import __version__
5 import natsort
156 from natsort.compat.py23 import py23_str
16
17
18 def main():
19 """\
7 from natsort.utils import regex_chooser
8
9
10 def main(*arguments):
11 """
2012 Performs a natural sort on entries given on the command-line.
21 A natural sort sorts numerically then alphabetically, and will sort
22 by numbers in the middle of an entry.
13
14 Arguments are read from sys.argv.
2315 """
2416
2517 from argparse import ArgumentParser, RawDescriptionHelpFormatter
2618 from textwrap import dedent
27 parser = ArgumentParser(description=dedent(main.__doc__),
28 formatter_class=RawDescriptionHelpFormatter)
29 parser.add_argument('--version', action='version',
30 version='%(prog)s {0}'.format(__version__))
31 parser.add_argument(
32 '-p', '--paths', default=False, action='store_true',
33 help='Interpret the input as file paths. This is not '
34 'strictly necessary to sort all file paths, but in cases '
35 'where there are OS-generated file paths like "Folder/" '
36 'and "Folder (1)/", this option is needed to make the '
37 'paths sorted in the order you expect ("Folder/" before '
38 '"Folder (1)/").')
39 parser.add_argument(
40 '-f', '--filter', nargs=2, type=float, metavar=('LOW', 'HIGH'),
41 action='append',
42 help='Used for keeping only the entries that have a number '
43 'falling in the given range.')
44 parser.add_argument(
45 '-F', '--reverse-filter', nargs=2, type=float,
46 metavar=('LOW', 'HIGH'), action='append', dest='reverse_filter',
47 help='Used for excluding the entries that have a number '
48 'falling in the given range.')
49 parser.add_argument(
50 '-e', '--exclude', type=float, action='append',
51 help='Used to exclude an entry that contains a specific number.')
52 parser.add_argument(
53 '-r', '--reverse', action='store_true', default=False,
54 help='Returns in reversed order.')
55 parser.add_argument(
56 '-t', '--number-type', '--number_type', dest='number_type',
57 choices=('digit', 'int', 'float', 'version', 'ver',
58 'real', 'f', 'i', 'r', 'd'),
59 default='int',
19
20 parser = ArgumentParser(
21 description=dedent(main.__doc__), formatter_class=RawDescriptionHelpFormatter
22 )
23 parser.add_argument(
24 "--version",
25 action="version",
26 version="%(prog)s {0}".format(natsort.__version__),
27 )
28 parser.add_argument(
29 "-p",
30 "--paths",
31 default=False,
32 action="store_true",
33 help="Interpret the input as file paths. This is not "
34 "strictly necessary to sort all file paths, but in cases "
35 'where there are OS-generated file paths like "Folder/" '
36 'and "Folder (1)/", this option is needed to make the '
37 'paths sorted in the order you expect ("Folder/" before '
38 '"Folder (1)/").',
39 )
40 parser.add_argument(
41 "-f",
42 "--filter",
43 nargs=2,
44 type=float,
45 metavar=("LOW", "HIGH"),
46 action="append",
47 help="Used for keeping only the entries that have a number "
48 "falling in the given range.",
49 )
50 parser.add_argument(
51 "-F",
52 "--reverse-filter",
53 nargs=2,
54 type=float,
55 metavar=("LOW", "HIGH"),
56 action="append",
57 dest="reverse_filter",
58 help="Used for excluding the entries that have a number "
59 "falling in the given range.",
60 )
61 parser.add_argument(
62 "-e",
63 "--exclude",
64 type=float,
65 action="append",
66 help="Used to exclude an entry that contains a specific number.",
67 )
68 parser.add_argument(
69 "-r",
70 "--reverse",
71 action="store_true",
72 default=False,
73 help="Returns in reversed order.",
74 )
75 parser.add_argument(
76 "-t",
77 "--number-type",
78 "--number_type",
79 dest="number_type",
80 choices=("digit", "int", "float", "version", "ver", "real", "f", "i", "r", "d"),
81 default="int",
6082 help='Choose the type of number to search for. "float" will search '
61 'for floating-point numbers. "int" will only search for '
62 'integers. "digit", "version", and "ver" are synonyms for "int".'
63 '"real" is a shortcut for "float" with --sign. '
64 '"i" and "d" are synonyms for "int", "f" is a synonym for '
65 '"float", and "r" is a synonym for "real".'
66 'The default is %(default)s.')
67 parser.add_argument(
68 '--nosign', default=False, action='store_false', dest='signed',
83 'for floating-point numbers. "int" will only search for '
84 'integers. "digit", "version", and "ver" are synonyms for "int".'
85 '"real" is a shortcut for "float" with --sign. '
86 '"i" and "d" are synonyms for "int", "f" is a synonym for '
87 '"float", and "r" is a synonym for "real".'
88 "The default is %(default)s.",
89 )
90 parser.add_argument(
91 "--nosign",
92 default=False,
93 action="store_false",
94 dest="signed",
6995 help='Do not consider "+" or "-" as part of a number, i.e. do not '
70 'take sign into consideration. This is the default.')
71 parser.add_argument(
72 '-s', '--sign', default=False, action='store_true', dest='signed',
96 "take sign into consideration. This is the default.",
97 )
98 parser.add_argument(
99 "-s",
100 "--sign",
101 default=False,
102 action="store_true",
103 dest="signed",
73104 help='Consider "+" or "-" as part of a number, i.e. '
74 'take sign into consideration. The default is unsigned.')
75 parser.add_argument(
76 '--noexp', default=True, action='store_false', dest='exp',
77 help='Do not consider an exponential as part of a number, i.e. 1e4, '
78 'would be considered as 1, "e", and 4, not as 10000. This only '
79 'effects the --number-type=float.')
80 parser.add_argument(
81 '-l', '--locale', action='store_true', default=False,
82 help='Causes natsort to use locale-aware sorting. You will get the '
83 'best results if you install PyICU.')
84 parser.add_argument(
85 'entries', nargs='*', default=sys.stdin,
86 help='The entries to sort. Taken from stdin if nothing is given on '
87 'the command line.', )
88 args = parser.parse_args()
105 "take sign into consideration. The default is unsigned.",
106 )
107 parser.add_argument(
108 "--noexp",
109 default=True,
110 action="store_false",
111 dest="exp",
112 help="Do not consider an exponential as part of a number, i.e. 1e4, "
113 'would be considered as 1, "e", and 4, not as 10000. This only '
114 "effects the --number-type=float.",
115 )
116 parser.add_argument(
117 "-l",
118 "--locale",
119 action="store_true",
120 default=False,
121 help="Causes natsort to use locale-aware sorting. You will get the "
122 "best results if you install PyICU.",
123 )
124 parser.add_argument(
125 "entries",
126 nargs="*",
127 default=sys.stdin,
128 help="The entries to sort. Taken from stdin if nothing is given on "
129 "the command line.",
130 )
131 args = parser.parse_args(arguments or None)
89132
90133 # Make sure the filter range is given properly. Does nothing if no filter
91 args.filter = check_filter(args.filter)
92 args.reverse_filter = check_filter(args.reverse_filter)
134 args.filter = check_filters(args.filter)
135 args.reverse_filter = check_filters(args.reverse_filter)
93136
94137 # Remove trailing whitespace from all the entries
95138 entries = [e.strip() for e in args.entries]
99142
100143
101144 def range_check(low, high):
102 """\
103 Verifies that that given range has a low lower than the high.
104 If the condition is not met, a ValueError is raised.
105 Otherwise the input is returned as-is.
145 """
146 Verify that that given range has a low lower than the high.
147
148 Parameters
149 ----------
150 low : {float, int}
151 high : {float, int}
152
153 Returns
154 -------
155 tuple : low, high
156
157 Raises
158 ------
159 ValueError
160 Low is greater than or equal to high.
161
106162 """
107163 if low >= high:
108 raise ValueError('low >= high')
164 raise ValueError("low >= high")
109165 else:
110166 return low, high
111167
112168
113 def check_filter(filt):
114 """\
115 Check that the low value of the filter is lower than the high.
116 If there is to be no filter, return 'None'.
117 If the condition is not met, a ValueError is raised.
118 Otherwise, the values are returned as-is.
119 """
120 # Quick return if no filter.
121 if not filt:
169 def check_filters(filters):
170 """
171 Execute range_check for every element of an iterable.
172
173 Parameters
174 ----------
175 filters : iterable
176 The collection of filters to check. Each element
177 must be a two-element tuple of floats or ints.
178
179 Returns
180 -------
181 The input as-is, or None if it evaluates to False.
182
183 Raises
184 ------
185 ValueError
186 Low is greater than or equal to high for any element.
187
188 """
189 if not filters:
122190 return None
123191 try:
124 return [range_check(f[0], f[1]) for f in filt]
125 except ValueError as a:
126 raise ValueError('Error in --filter: '+py23_str(a))
192 return [range_check(f[0], f[1]) for f in filters]
193 except ValueError as err:
194 raise ValueError("Error in --filter: " + py23_str(err))
127195
128196
129197 def keep_entry_range(entry, lows, highs, converter, regex):
130 """\
131 Boolean function to determine if an entry should be kept out
132 based on if any numbers are in a given range.
133
134 Returns True if it should be kept (i.e. falls in the range),
135 and False if it is not in the range and should not be kept.
136 """
137 return any(low <= converter(num) <= high
138 for num in regex.findall(entry)
139 for low, high in zip(lows, highs))
140
141
142 def exclude_entry(entry, values, converter, regex):
143 """\
144 Boolean function to determine if an entry should be kept out
145 based on if it contains a specific number.
146
147 Returns True if it should be kept (i.e. does not match),
148 and False if it matches and should not be kept.
198 """
199 Check if an entry falls into a desired range.
200
201 Every number in the entry will be extracted using *regex*,
202 if any are within a given low to high range the entry will
203 be kept.
204
205 Parameters
206 ----------
207 entry : str
208 lows : iterable
209 Collection of low values against which to compare the entry.
210 highs : iterable
211 Collection of high values against which to compare the entry.
212 converter : callable
213 Function to convert a string to a number.
214 regex : regex object
215 Regular expression to locate numbers in a string.
216
217 Returns
218 -------
219 True if the entry should be kept, False otherwise.
220
221 """
222 return any(
223 low <= converter(num) <= high
224 for num in regex.findall(entry)
225 for low, high in zip(lows, highs)
226 )
227
228
229 def keep_entry_value(entry, values, converter, regex):
230 """
231 Check if an entry does not match a given value.
232
233 Every number in the entry will be extracted using *regex*,
234 if any match a given value the entry will not be kept.
235
236 Parameters
237 ----------
238 entry : str
239 values : iterable
240 Collection of values against which to compare the entry.
241 converter : callable
242 Function to convert a string to a number.
243 regex : regex object
244 Regular expression to locate numbers in a string.
245
246 Returns
247 -------
248 True if the entry should be kept, False otherwise.
249
149250 """
150251 return not any(converter(num) in values for num in regex.findall(entry))
151252
154255 """Sort the entries, applying the filters first if necessary."""
155256
156257 # Extract the proper number type.
157 is_float = args.number_type in ('float', 'real', 'f', 'r')
158 signed = args.signed or args.number_type in ('real', 'r')
159 alg = (ns.FLOAT * is_float |
160 ns.SIGNED * signed |
161 ns.NOEXP * (not args.exp) |
162 ns.PATH * args.paths |
163 ns.LOCALE * args.locale)
258 is_float = args.number_type in ("float", "real", "f", "r")
259 signed = args.signed or args.number_type in ("real", "r")
260 alg = (
261 natsort.ns.FLOAT * is_float
262 | natsort.ns.SIGNED * signed
263 | natsort.ns.NOEXP * (not args.exp)
264 | natsort.ns.PATH * args.paths
265 | natsort.ns.LOCALE * args.locale
266 )
164267
165268 # Pre-remove entries that don't pass the filtering criteria
166269 # Make sure we use the same searching algorithm for filtering
167270 # as for sorting.
168271 do_filter = args.filter is not None or args.reverse_filter is not None
169272 if do_filter or args.exclude:
170 inp_options = (ns.FLOAT * is_float |
171 ns.SIGNED * signed |
172 ns.NOEXP * (not args.exp)
173 )
174 regex = _regex_chooser[inp_options]
273 inp_options = (
274 natsort.ns.FLOAT * is_float
275 | natsort.ns.SIGNED * signed
276 | natsort.ns.NOEXP * (not args.exp)
277 )
278 regex = regex_chooser(inp_options)
175279 if args.filter is not None:
176 lows, highs = ([f[0] for f in args.filter],
177 [f[1] for f in args.filter])
178 entries = [entry for entry in entries
179 if keep_entry_range(entry, lows, highs,
180 float, regex)]
280 lows, highs = ([f[0] for f in args.filter], [f[1] for f in args.filter])
281 entries = [
282 entry
283 for entry in entries
284 if keep_entry_range(entry, lows, highs, float, regex)
285 ]
181286 if args.reverse_filter is not None:
182 lows, highs = ([f[0] for f in args.reverse_filter],
183 [f[1] for f in args.reverse_filter])
184 entries = [entry for entry in entries
185 if not keep_entry_range(entry, lows, highs,
186 float, regex)]
287 lows, highs = (
288 [f[0] for f in args.reverse_filter],
289 [f[1] for f in args.reverse_filter],
290 )
291 entries = [
292 entry
293 for entry in entries
294 if not keep_entry_range(entry, lows, highs, float, regex)
295 ]
187296 if args.exclude:
188297 exclude = set(args.exclude)
189 entries = [entry for entry in entries
190 if exclude_entry(entry, exclude,
191 float, regex)]
298 entries = [
299 entry
300 for entry in entries
301 if keep_entry_value(entry, exclude, float, regex)
302 ]
192303
193304 # Print off the sorted results
194 for entry in natsorted(entries, reverse=args.reverse, alg=alg):
305 for entry in natsort.natsorted(entries, reverse=args.reverse, alg=alg):
195306 print(entry)
196307
197308
198 if __name__ == '__main__':
309 if __name__ == "__main__":
199310 try:
200311 main()
201312 except ValueError as a:
+0
-9
natsort/_version.py less more
0 # -*- coding: utf-8 -*-
1 from __future__ import (
2 print_function,
3 division,
4 unicode_literals,
5 absolute_import
6 )
7
8 __version__ = '5.3.3'
00 # -*- coding: utf-8 -*-
1 """\
1 """
22 This module is intended to replicate some of the functionality
3 from the fastnumbers module in the event that module is not
4 installed.
3 from the fastnumbers module in the event that module is not installed.
54 """
6 from __future__ import (
7 print_function,
8 division,
9 unicode_literals,
10 absolute_import
11 )
5 from __future__ import absolute_import, division, print_function, unicode_literals
126
137 # Std. lib imports.
148 import unicodedata
9
10 # Local imports.
11 from natsort.compat.py23 import PY_VERSION
1512 from natsort.unicode_numbers import decimal_chars
16 from natsort.compat.py23 import PY_VERSION
13
1714 if PY_VERSION >= 3:
1815 long = int
1916
2017
21 NAN_INF = ['INF', 'INf', 'Inf', 'inF', 'iNF', 'InF', 'inf', 'iNf',
22 'NAN', 'nan', 'NaN', 'nAn', 'naN', 'NAn', 'nAN', 'Nan']
23 NAN_INF.extend(['+'+x[:2] for x in NAN_INF] + ['-'+x[:2] for x in NAN_INF])
18 NAN_INF = [
19 "INF",
20 "INf",
21 "Inf",
22 "inF",
23 "iNF",
24 "InF",
25 "inf",
26 "iNf",
27 "NAN",
28 "nan",
29 "NaN",
30 "nAn",
31 "naN",
32 "NAn",
33 "nAN",
34 "Nan",
35 ]
36 NAN_INF.extend(["+" + x[:2] for x in NAN_INF] + ["-" + x[:2] for x in NAN_INF])
2437 NAN_INF = frozenset(NAN_INF)
25 ASCII_NUMS = '0123456789+-'
38 ASCII_NUMS = "0123456789+-"
39 POTENTIAL_FIRST_CHAR = frozenset(decimal_chars + list(ASCII_NUMS + "."))
2640
2741
28 def fast_float(x, key=lambda x: x, nan=None,
29 uni=unicodedata.numeric, nan_inf=NAN_INF,
30 _first_char=frozenset(decimal_chars + list(ASCII_NUMS + '.'))):
31 """\
42 # noinspection PyIncorrectDocstring
43 def fast_float(
44 x,
45 key=lambda x: x,
46 nan=None,
47 _uni=unicodedata.numeric,
48 _nan_inf=NAN_INF,
49 _first_char=POTENTIAL_FIRST_CHAR,
50 ):
51 """
3252 Convert a string to a float quickly, return input as-is if not possible.
53
3354 We don't need to accept all input that the real fast_int accepts because
34 the input will be controlled by the splitting algorithm.
55 natsort is controlling what is passed to this function.
56
57 Parameters
58 ----------
59 x : str
60 String to attempt to convert to a float.
61 key : callable
62 Single-argument function to apply to *x* if conversion fails.
63 nan : object
64 Value to return instead of NaN if NaN would be returned.
65
66 Returns
67 -------
68 *str* or *float*
69
3570 """
36 if x[0] in _first_char or x.lstrip()[:3] in nan_inf:
71 if x[0] in _first_char or x.lstrip()[:3] in _nan_inf:
3772 try:
3873 x = float(x)
3974 return nan if nan is not None and x != x else x
4075 except ValueError:
4176 try:
42 return uni(x, key(x)) if len(x) == 1 else key(x)
77 return _uni(x, key(x)) if len(x) == 1 else key(x)
4378 except TypeError: # pragma: no cover
4479 return key(x)
4580 else:
4681 try:
47 return uni(x, key(x)) if len(x) == 1 else key(x)
82 return _uni(x, key(x)) if len(x) == 1 else key(x)
4883 except TypeError: # pragma: no cover
4984 return key(x)
5085
5186
52 def fast_int(x, key=lambda x: x, nan=None, uni=unicodedata.digit,
53 _first_char=frozenset(decimal_chars + list(ASCII_NUMS))):
54 """\
87 # noinspection PyIncorrectDocstring
88 def fast_int(
89 x,
90 key=lambda x: x,
91 _uni=unicodedata.digit,
92 _first_char=POTENTIAL_FIRST_CHAR,
93 ):
94 """
5595 Convert a string to a int quickly, return input as-is if not possible.
96
5697 We don't need to accept all input that the real fast_int accepts because
57 the input will be controlled by the splitting algorithm.
98 natsort is controlling what is passed to this function.
99
100 Parameters
101 ----------
102 x : str
103 String to attempt to convert to an int.
104 key : callable
105 Single-argument function to apply to *x* if conversion fails.
106
107 Returns
108 -------
109 *str* or *int*
110
58111 """
59112 if x[0] in _first_char:
60113 try:
61114 return long(x)
62115 except ValueError:
63116 try:
64 return uni(x, key(x)) if len(x) == 1 else key(x)
117 return _uni(x, key(x)) if len(x) == 1 else key(x)
65118 except TypeError: # pragma: no cover
66119 return key(x)
67120 else:
68121 try:
69 return uni(x, key(x)) if len(x) == 1 else key(x)
122 return _uni(x, key(x)) if len(x) == 1 else key(x)
70123 except TypeError: # pragma: no cover
71124 return key(x)
00 # -*- coding: utf-8 -*-
1 from __future__ import (
2 print_function,
3 division,
4 unicode_literals,
5 absolute_import
6 )
1 """
2 Interface for natsort to access fastnumbers functions without
3 having to worry if it is actually installed.
4 """
5 from __future__ import absolute_import, division, print_function, unicode_literals
76
87 from distutils.version import StrictVersion
98
109 # If the user has fastnumbers installed, they will get great speed
1110 # benefits. If not, we use the simulated functions that come with natsort.
1211 try:
13 from fastnumbers import (
14 fast_float,
15 fast_int,
16 )
17 import fastnumbers
18 # Require >= version 0.7.1.
19 if StrictVersion(fastnumbers.__version__) < StrictVersion('0.7.1'):
12 # noinspection PyPackageRequirements
13 from fastnumbers import fast_float, fast_int, __version__ as fn_ver
14
15 # Require >= version 2.0.0.
16 if StrictVersion(fn_ver) < StrictVersion("2.0.0"):
2017 raise ImportError # pragma: no cover
2118 except ImportError:
22 from natsort.compat.fake_fastnumbers import (
23 fast_float,
24 fast_int,
25 )
19 from natsort.compat.fake_fastnumbers import fast_float, fast_int # noqa: F401
00 # -*- coding: utf-8 -*-
1 from __future__ import (
2 print_function,
3 division,
4 unicode_literals,
5 absolute_import
6 )
1 """
2 Interface for natsort to access locale functionality without
3 having to worry about if it is using PyICU or the built-in locale.
4 """
5 from __future__ import absolute_import, division, print_function, unicode_literals
76
87 # Std. lib imports.
98 import sys
109
1110 # Local imports.
12 from natsort.compat.py23 import (
13 PY_VERSION,
14 cmp_to_key,
15 py23_unichr,
16 )
11 from natsort.compat.py23 import PY_VERSION, cmp_to_key, py23_unichr
1712
1813 # This string should be sorted after any other byte string because
1914 # it contains the max unicode character repeated 20 times.
2015 # You would need some odd data to come after that.
21 null_string = ''
16 null_string = ""
2217 null_string_max = py23_unichr(sys.maxunicode) * 20
2318
2419 # Make the strxfrm function from strcoll on Python2
2520 # It can be buggy (especially on BSD-based systems),
2621 # so prefer icu if available.
27 try:
22 try: # noqa: C901
2823 import icu
2924 from locale import getlocale
3025
31 null_string_locale = b''
26 null_string_locale = b""
3227
3328 # This string should in theory be sorted after any other byte
3429 # string because it contains the max byte char repeated many times.
3530 # You would need some odd data to come after that.
36 null_string_locale_max = b'x7f' * 50
31 null_string_locale_max = b"x7f" * 50
3732
3833 def dumb_sort():
3934 return False
4136 # If using icu, get the locale from the current global locale,
4237 def get_icu_locale():
4338 try:
44 return icu.Locale('.'.join(getlocale()))
39 return icu.Locale(".".join(getlocale()))
4540 except TypeError: # pragma: no cover
4641 return icu.Locale()
4742
5651 sep = icu.DecimalFormatSymbols.kDecimalSeparatorSymbol
5752 return icu.DecimalFormatSymbols(get_icu_locale()).getSymbol(sep)
5853
54
5955 except ImportError:
6056 import locale
57
6158 if PY_VERSION < 3:
6259 from locale import strcoll
60
6361 sentinel = object()
6462
6563 def custom_strcoll(a, b, last=sentinel):
7270 return strcoll(a, b)
7371
7472 strxfrm = cmp_to_key(custom_strcoll)
75 null_string_locale = strxfrm('')
73 null_string_locale = strxfrm("")
7674 null_string_locale_max = strxfrm(sentinel)
7775 else:
7876 from locale import strxfrm
79 null_string_locale = ''
77
78 null_string_locale = ""
8079
8180 # This string should be sorted after any other byte string because
8281 # it contains the max unicode character repeated 20 times.
8685 # On some systems, locale is broken and does not sort in the expected
8786 # order. We will try to detect this and compensate.
8887 def dumb_sort():
89 return strxfrm('A') < strxfrm('a')
88 return strxfrm("A") < strxfrm("a")
9089
9190 def get_strxfrm():
9291 return strxfrm
9392
9493 def get_thousands_sep():
95 sep = locale.localeconv()['thousands_sep']
94 sep = locale.localeconv()["thousands_sep"]
9695 # If this locale library is broken, some of the thousands separator
9796 # characters are incorrectly blank. Here is a lookup table of the
9897 # corrections I am aware of.
9998 if dumb_sort():
10099 try:
101 loc = '.'.join(locale.getlocale())
100 loc = ".".join(locale.getlocale())
102101 except TypeError: # No locale loaded, default to ','
103 return ','
104 return {'de_DE.ISO8859-15': '.',
105 'es_ES.ISO8859-1': '.',
106 'de_AT.ISO8859-1': '.',
107 'de_at': '\xa0',
108 'nl_NL.UTF-8': '.',
109 'es_es': '.',
110 'fr_CH.ISO8859-15': '\xa0',
111 'fr_CA.ISO8859-1': '\xa0',
112 'de_CH.ISO8859-1': '.',
113 'fr_FR.ISO8859-15': '\xa0',
114 'nl_NL.ISO8859-1': '.',
115 'ca_ES.UTF-8': '.',
116 'nl_NL.ISO8859-15': '.',
117 'de_ch': "'",
118 'ca_es': '.',
119 'de_AT.ISO8859-15': '.',
120 'ca_ES.ISO8859-1': '.',
121 'de_AT.UTF-8': '.',
122 'es_ES.UTF-8': '.',
123 'fr_fr': '\xa0',
124 'es_ES.ISO8859-15': '.',
125 'de_DE.ISO8859-1': '.',
126 'nl_nl': '.',
127 'fr_ch': '\xa0',
128 'fr_ca': '\xa0',
129 'de_DE.UTF-8': '.',
130 'ca_ES.ISO8859-15': '.',
131 'de_CH.ISO8859-15': '.',
132 'fr_FR.ISO8859-1': '\xa0',
133 'fr_CH.ISO8859-1': '\xa0',
134 'de_de': '.',
135 'fr_FR.UTF-8': '\xa0',
136 'fr_CA.ISO8859-15': '\xa0',
137 }.get(loc, sep)
102 return ","
103 return {
104 "de_DE.ISO8859-15": ".",
105 "es_ES.ISO8859-1": ".",
106 "de_AT.ISO8859-1": ".",
107 "de_at": "\xa0",
108 "nl_NL.UTF-8": ".",
109 "es_es": ".",
110 "fr_CH.ISO8859-15": "\xa0",
111 "fr_CA.ISO8859-1": "\xa0",
112 "de_CH.ISO8859-1": ".",
113 "fr_FR.ISO8859-15": "\xa0",
114 "nl_NL.ISO8859-1": ".",
115 "ca_ES.UTF-8": ".",
116 "nl_NL.ISO8859-15": ".",
117 "de_ch": "'",
118 "ca_es": ".",
119 "de_AT.ISO8859-15": ".",
120 "ca_ES.ISO8859-1": ".",
121 "de_AT.UTF-8": ".",
122 "es_ES.UTF-8": ".",
123 "fr_fr": "\xa0",
124 "es_ES.ISO8859-15": ".",
125 "de_DE.ISO8859-1": ".",
126 "nl_nl": ".",
127 "fr_ch": "\xa0",
128 "fr_ca": "\xa0",
129 "de_DE.UTF-8": ".",
130 "ca_ES.ISO8859-15": ".",
131 "de_CH.ISO8859-15": ".",
132 "fr_FR.ISO8859-1": "\xa0",
133 "fr_CH.ISO8859-1": "\xa0",
134 "de_de": ".",
135 "fr_FR.UTF-8": "\xa0",
136 "fr_CA.ISO8859-15": "\xa0",
137 }.get(loc, sep)
138138 else:
139139 return sep
140140
141141 def get_decimal_point():
142 return locale.localeconv()['decimal_point']
142 return locale.localeconv()["decimal_point"]
00 # -*- coding: utf-8 -*-
1 from __future__ import (
2 print_function,
3 division,
4 unicode_literals,
5 absolute_import
6 )
1 from __future__ import absolute_import, division, print_function, unicode_literals
72
83 try:
94 from pathlib import PurePath # PurePath is the base object for Paths.
00 # -*- coding: utf-8 -*-
1 from __future__ import (
2 print_function,
3 division,
4 unicode_literals,
5 absolute_import
6 )
1 """
2 Compatibility layer for Python 2 and Python 3.
3
4 Probably could have used six...
5 This file will light up most linters...
6 """
7 from __future__ import absolute_import, division, print_function, unicode_literals
78
89 import functools
910 import sys
1819 NEWPY = PY_VERSION >= 3.3
1920
2021 # Assume all strings are Unicode in Python 2
21 py23_str = str if sys.version[0] == '3' else unicode
22 py23_str = str if PY_VERSION >= 3 else unicode
2223
2324 # Use the range iterator always
24 py23_range = range if sys.version[0] == '3' else xrange
25 py23_range = range if PY_VERSION >= 3 else xrange
2526
2627 # Uniform base string type
27 py23_basestring = str if sys.version[0] == '3' else basestring
28 py23_basestring = str if PY_VERSION >= 3 else basestring
29
30 # Iniform int type
31 py23_int = (int,) if PY_VERSION >= 3 else (int, long)
2832
2933 # unichr function
30 py23_unichr = chr if sys.version[0] == '3' else unichr
34 py23_unichr = chr if PY_VERSION >= 3 else unichr
35
36 # Proper lower-casing of letters.
37 py23_lower = py23_str.casefold if NEWPY else py23_str.lower
3138
3239
3340 def _py23_cmp(a, b):
3441 return (a > b) - (a < b)
3542
3643
37 py23_cmp = _py23_cmp if sys.version[0] == '3' else cmp
44 py23_cmp = _py23_cmp if PY_VERSION >= 3 else cmp
3845
3946 # zip as an iterator
40 if sys.version[0] == '3':
47 if PY_VERSION >= 3:
4148 py23_zip = zip
4249 py23_map = map
4350 py23_filter = filter
4451 else:
4552 import itertools
53
4654 py23_zip = itertools.izip
4755 py23_map = itertools.imap
4856 py23_filter = itertools.ifilter
49
5057
5158 # cmp_to_key was not created till 2.7, so require this for 2.6
5259 try:
5360 from functools import cmp_to_key
5461 except ImportError: # pragma: no cover
62
5563 def cmp_to_key(mycmp):
5664 """Convert a cmp= function into a key= function"""
65
5766 class K(object):
58 __slots__ = ['obj']
67 __slots__ = ["obj"]
5968
6069 def __init__(self, obj):
6170 self.obj = obj
7988 return mycmp(self.obj, other.obj) != 0
8089
8190 def __hash__(self):
82 raise TypeError('hash not implemented')
91 raise TypeError("hash not implemented")
8392
8493 return K
8594
103112 func.__doc__ = doc
104113 return func
105114 return doc
115
106116 return wrapper
107117
108118
109119 # Properly modify a doctstring to either have the unicode literal or not.
110 if sys.version[0] == '3':
120 if PY_VERSION >= 3:
111121 # Abstract u'abc' syntax:
112122 @_modify_str_or_docstring
113123 def u_format(s):
114124 """"{u}'abc'" --> "'abc'" (Python 3)
115125
116126 Accepts a string or a function, so it can be used as a decorator."""
117 return s.format(u='')
127 return s.format(u="")
128
129
118130 else:
119131 # Abstract u'abc' syntax:
120132 @_modify_str_or_docstring
122134 """"{u}'abc'" --> "u'abc'" (Python 2)
123135
124136 Accepts a string or a function, so it can be used as a decorator."""
125 return s.format(u='u')
137 return s.format(u="u")
00 # -*- coding: utf-8 -*-
11 """
2 Natsort can sort strings with numbers in a natural order.
3 It provides the natsorted function to sort strings with
4 arbitrary numbers.
5
6 You can mix types with natsorted. This can get around the new
7 'unorderable types' issue with Python 3. Natsort will recursively
8 descend into lists of lists so you can sort by the sublist contents.
9
10 See the README or the natsort homepage for more details.
2 Along with ns_enum.py, this module contains all of the
3 natsort public API.
4
5 The majority of the "work" is defined in utils.py.
116 """
12 from __future__ import (
13 print_function,
14 division,
15 unicode_literals,
16 absolute_import
17 )
18
19 # Std lib. imports.
7 from __future__ import absolute_import, division, print_function, unicode_literals
8
9 import sys
10 from functools import partial
2011 from operator import itemgetter
21 from functools import partial
22 from warnings import warn
23
24 # Local imports.
25 import sys
2612
2713 import natsort.compat.locale
28 from natsort.ns_enum import ns
29 from natsort.compat.py23 import (
30 u_format,
31 py23_str,
32 py23_cmp)
33 from natsort.utils import (
34 _natsort_key,
35 _args_to_enum,
36 _do_decoding,
37 _regex_chooser,
38 _parse_string_factory,
39 _parse_path_factory,
40 _parse_number_factory,
41 _parse_bytes_factory,
42 _input_string_transform_factory,
43 _string_component_transform_factory,
44 _final_data_transform_factory,
45 )
46
47 # Make sure the doctest works for either python2 or python3
48 __doc__ = u_format(__doc__)
14 from natsort import utils
15 from natsort.compat.py23 import py23_cmp, py23_str, u_format
16 from natsort.ns_enum import ns, ns_DUMB
4917
5018
5119 @u_format
5523
5624 Parameters
5725 ----------
58 encoding: str
26 encoding : str
5927 The codec to use for decoding. This must be a valid unicode codec.
6028
6129 Returns
6230 -------
63 decode_function:
31 decode_function
6432 A function that takes a single argument and attempts to decode
6533 it using the supplied codec. Any `UnicodeErrors` are raised.
6634 If the argument was not of `bytes` type, it is simply returned
8755 True
8856
8957 """
90 return partial(_do_decoding, encoding=encoding)
58 return partial(utils.do_decoding, encoding=encoding)
9159
9260
9361 @u_format
9765
9866 Parameters
9967 ----------
100 s:
101 Any object.
102
103 Returns
104 -------
105 output:
68 s : object
69
70 Returns
71 -------
72 output
10673 If the input was of type `bytes`, the return value is a `str` decoded
10774 with the ASCII codec. Otherwise, the return value is identically the
10875 input.
11279 decoder
11380
11481 """
115 return _do_decoding(s, 'ascii')
82 return utils.do_decoding(s, "ascii")
11683
11784
11885 @u_format
12289
12390 Parameters
12491 ----------
125 s:
126 Any object.
127
128 Returns
129 -------
130 output:
92 s : object
93
94 Returns
95 -------
96 output
13197 If the input was of type `bytes`, the return value is a `str` decoded
13298 with the UTF-8 codec. Otherwise, the return value is identically the
13399 input.
137103 decoder
138104
139105 """
140 return _do_decoding(s, 'utf-8')
141
142
143 def natsort_key(val, key=None, alg=0, **_kwargs):
144 """Undocumented, kept for backwards-compatibility."""
145 msg = "natsort_key is deprecated as of 3.4.0, please use natsort_keygen"
146 warn(msg, DeprecationWarning)
147 return natsort_keygen(key, alg, **_kwargs)(val)
148
149
150 @u_format
151 def natsort_keygen(key=None, alg=0, **_kwargs):
152 """\
106 return utils.do_decoding(s, "utf-8")
107
108
109 @u_format
110 def natsort_keygen(key=None, alg=ns.DEFAULT, **_kwargs):
111 """
153112 Generate a key to sort strings and numbers naturally.
154113
155 Generate a key to sort strings and numbers naturally,
156 not lexicographically. This key is designed for use as the
157 `key` argument to functions such as the `sorted` builtin.
114 This key is designed for use as the `key` argument to
115 functions such as the `sorted` builtin.
158116
159117 The user may customize the generated function with the
160118 arguments to `natsort_keygen`, including an optional
182140 See Also
183141 --------
184142 natsorted
143 natsort_key
185144
186145 Examples
187146 --------
196155 """
197156 # Transform old arguments to the ns enum.
198157 try:
199 alg = _args_to_enum(**_kwargs) | alg
158 alg = utils.args_to_enum(**_kwargs) | alg
200159 except TypeError:
201160 msg = "natsort_keygen: 'alg' argument must be from the enum 'ns'"
202 raise ValueError(msg+', got {0}'.format(py23_str(alg)))
161 raise ValueError(msg + ", got {0}".format(py23_str(alg)))
203162
204163 # Add the _DUMB option if the locale library is broken.
205164 if alg & ns.LOCALEALPHA and natsort.compat.locale.dumb_sort():
206 alg |= ns._DUMB
165 alg |= ns_DUMB
207166
208167 # Set some variables that will be passed to the factory functions
209168 if alg & ns.NUMAFTER:
218177 else:
219178 sep = natsort.compat.locale.null_string
220179 pre_sep = natsort.compat.locale.null_string
221 regex = _regex_chooser[alg & ns._NUMERIC_ONLY]
180 regex = utils.regex_chooser(alg)
222181
223182 # Create the functions that will be used to split strings.
224 input_transform = _input_string_transform_factory(alg)
225 component_transform = _string_component_transform_factory(alg)
226 final_transform = _final_data_transform_factory(alg, sep, pre_sep)
183 input_transform = utils.input_string_transform_factory(alg)
184 component_transform = utils.string_component_transform_factory(alg)
185 final_transform = utils.final_data_transform_factory(alg, sep, pre_sep)
227186
228187 # Create the high-level parsing functions for strings, bytes, and numbers.
229 string_func = _parse_string_factory(
230 alg, sep, regex.split,
231 input_transform, component_transform, final_transform
188 string_func = utils.parse_string_factory(
189 alg, sep, regex.split, input_transform, component_transform, final_transform
232190 )
233191 if alg & ns.PATH:
234 string_func = _parse_path_factory(string_func)
235 bytes_func = _parse_bytes_factory(alg)
236 num_func = _parse_number_factory(alg, sep, pre_sep)
192 string_func = utils.parse_path_factory(string_func)
193 bytes_func = utils.parse_bytes_factory(alg)
194 num_func = utils.parse_number_factory(alg, sep, pre_sep)
237195
238196 # Return the natsort key with the parsing path pre-chosen.
239197 return partial(
240 _natsort_key,
198 utils.natsort_key,
241199 key=key,
242200 string_func=string_func,
243201 bytes_func=bytes_func,
244 num_func=num_func
202 num_func=num_func,
245203 )
246204
247205
248 @u_format
249 def natsorted(seq, key=None, reverse=False, alg=0, **_kwargs):
250 """\
206 # Exposed for simplicity if one needs the default natsort key.
207 natsort_key = natsort_keygen()
208 natsort_key.__doc__ = """\
209 natsort_key(val)
210 The default natural sorting key.
211
212 This is the output of :func:`natsort_keygen` with default values.
213
214 See Also
215 --------
216 natsort_keygen
217
218 """
219
220
221 @u_format
222 def natsorted(seq, key=None, reverse=False, alg=ns.DEFAULT, **_kwargs):
223 """
251224 Sorts an iterable naturally.
252225
253 Sorts an iterable naturally (alphabetically and numerically),
254 not lexicographically. Returns a list containing a sorted copy
255 of the iterable.
256
257226 Parameters
258227 ----------
259228 seq : iterable
260 The iterable to sort.
229 The input to sort.
261230
262231 key : callable, optional
263232 A key used to determine how to sort each element of the iterable.
276245 Returns
277246 -------
278247 out: list
279 The sorted sequence.
248 The sorted input.
280249
281250 See Also
282251 --------
294263 [{u}'num2', {u}'num3', {u}'num5']
295264
296265 """
297 natsort_key = natsort_keygen(key, alg, **_kwargs)
298 return sorted(seq, reverse=reverse, key=natsort_key)
299
300
301 @u_format
302 def versorted(seq, key=None, reverse=False, alg=0, **_kwargs):
303 """\
266 key = natsort_keygen(key, alg, **_kwargs)
267 return sorted(seq, reverse=reverse, key=key)
268
269
270 @u_format
271 def versorted(seq, key=None, reverse=False, alg=ns.DEFAULT, **_kwargs):
272 """
304273 Identical to :func:`natsorted`.
305274
306275 This function exists for backwards compatibility with `natsort`
315284
316285
317286 @u_format
318 def humansorted(seq, key=None, reverse=False, alg=0):
319 """\
287 def humansorted(seq, key=None, reverse=False, alg=ns.DEFAULT):
288 """
320289 Convenience function to properly sort non-numeric characters.
321290
322 Convenience function to properly sort non-numeric characters
323 in a locale-aware fashion (a.k.a "human sorting"). This is a
324 wrapper around ``natsorted(seq, alg=ns.LOCALE)``.
291 This is a wrapper around ``natsorted(seq, alg=ns.LOCALE)``.
325292
326293 Parameters
327294 ----------
328295 seq : iterable
329 The sequence to sort.
296 The input to sort.
330297
331298 key : callable, optional
332299 A key used to determine how to sort each element of the sequence.
345312 Returns
346313 -------
347314 out : list
348 The sorted sequence.
315 The sorted input.
349316
350317 See Also
351318 --------
370337
371338
372339 @u_format
373 def realsorted(seq, key=None, reverse=False, alg=0):
374 """\
340 def realsorted(seq, key=None, reverse=False, alg=ns.DEFAULT):
341 """
375342 Convenience function to properly sort signed floats.
376343
377 Convenience function to properly sort signed floats within
378 strings (i.e. "a-5.7"). This is a wrapper around
344 A signed float in a string could be "a-5.7". This is a wrapper around
379345 ``natsorted(seq, alg=ns.REAL)``.
380346
381347 The behavior of :func:`realsorted` for `natsort` version >= 4.0.0
385351 Parameters
386352 ----------
387353 seq : iterable
388 The sequence to sort.
354 The input to sort.
389355
390356 key : callable, optional
391357 A key used to determine how to sort each element of the sequence.
404370 Returns
405371 -------
406372 out : list
407 The sorted sequence.
373 The sorted input.
408374
409375 See Also
410376 --------
425391
426392
427393 @u_format
428 def index_natsorted(seq, key=None, reverse=False, alg=0, **_kwargs):
429 """\
430 Return the list of the indexes used to sort the input sequence.
394 def index_natsorted(seq, key=None, reverse=False, alg=ns.DEFAULT, **_kwargs):
395 """
396 Determine the list of the indexes used to sort the input sequence.
431397
432398 Sorts a sequence naturally, but returns a list of sorted the
433 indexes and not the sorted list. This list of indexes can be
434 used to sort multiple lists by the sorted order of the given
435 sequence.
399 indexes and not the sorted list itself. This list of indexes
400 can be used to sort multiple lists by the sorted order of the
401 given sequence.
436402
437403 Parameters
438404 ----------
439405 seq : iterable
440 The sequence to sort.
406 The input to sort.
441407
442408 key : callable, optional
443409 A key used to determine how to sort each element of the sequence.
456422 Returns
457423 -------
458424 out : tuple
459 The ordered indexes of the sequence.
425 The ordered indexes of the input.
460426
461427 See Also
462428 --------
484450 if key is None:
485451 newkey = itemgetter(1)
486452 else:
453
487454 def newkey(x):
488455 return key(itemgetter(1)(x))
456
489457 # Pair the index and sequence together, then sort by element
490458 index_seq_pair = [[x, y] for x, y in enumerate(seq)]
491 index_seq_pair.sort(reverse=reverse,
492 key=natsort_keygen(newkey, alg, **_kwargs))
459 index_seq_pair.sort(reverse=reverse, key=natsort_keygen(newkey, alg, **_kwargs))
493460 return [x for x, _ in index_seq_pair]
494461
495462
496463 @u_format
497 def index_versorted(seq, key=None, reverse=False, alg=0, **_kwargs):
498 """\
464 def index_versorted(seq, key=None, reverse=False, alg=ns.DEFAULT, **_kwargs):
465 """
499466 Identical to :func:`index_natsorted`.
500467
501468 This function exists for backwards compatibility with
513480
514481
515482 @u_format
516 def index_humansorted(seq, key=None, reverse=False, alg=0):
517 """\
518 Return the list of the indexes used to sort the input sequence
519 in a locale-aware manner.
520
521 Sorts a sequence in a locale-aware manner, but returns a list
522 of sorted the indexes and not the sorted list. This list of
523 indexes can be used to sort multiple lists by the sorted order
524 of the given sequence.
525
483 def index_humansorted(seq, key=None, reverse=False, alg=ns.DEFAULT):
484 """
526485 This is a wrapper around ``index_natsorted(seq, alg=ns.LOCALE)``.
527486
528487 Parameters
529488 ----------
530489 seq: iterable
531 The sequence to sort.
490 The input to sort.
532491
533492 key: callable, optional
534493 A key used to determine how to sort each element of the sequence.
547506 Returns
548507 -------
549508 out : tuple
550 The ordered indexes of the sequence.
509 The ordered indexes of the input.
551510
552511 See Also
553512 --------
571530
572531
573532 @u_format
574 def index_realsorted(seq, key=None, reverse=False, alg=0):
575 """\
576 Return the list of the indexes used to sort the input sequence
577 in a locale-aware manner.
578
579 Sorts a sequence in a locale-aware manner, but returns a list
580 of sorted the indexes and not the sorted list. This list of
581 indexes can be used to sort multiple lists by the sorted order
582 of the given sequence.
583
533 def index_realsorted(seq, key=None, reverse=False, alg=ns.DEFAULT):
534 """
584535 This is a wrapper around ``index_natsorted(seq, alg=ns.REAL)``.
585536
586 The behavior of :func:`index_realsorted` in `natsort` version >= 4.0.0
587 was the default behavior of :func:`index_natsorted` for `natsort`
588 version < 4.0.0.
589
590537 Parameters
591538 ----------
592539 seq: iterable
593 The sequence to sort.
540 The input to sort.
594541
595542 key: callable, optional
596543 A key used to determine how to sort each element of the sequence.
609556 Returns
610557 -------
611558 out : tuple
612 The ordered indexes of the sequence.
559 The ordered indexes of the input.
613560
614561 See Also
615562 --------
628575 return index_natsorted(seq, key, reverse, alg | ns.REAL)
629576
630577
578 # noinspection PyShadowingBuiltins,PyUnresolvedReferences
631579 @u_format
632580 def order_by_index(seq, index, iter=False):
633 """\
581 """
634582 Order a given sequence by an index sequence.
635583
636584 The output of `index_natsorted` is a
690638
691639 if float(sys.version[:3]) < 3:
692640 # pylint: disable=unused-variable
693 class natcmp(object):
641 # noinspection PyUnresolvedReferences,PyPep8Naming
642 class natcmp(object): # noqa: N801
694643 """
695644 Compare two objects using a key and an algorithm.
696645
725674 >>> natcmp(one, two)
726675 -1
727676 """
677
728678 cached_keys = {}
729679
730 def __new__(cls, x, y, alg=0, *args, **kwargs):
680 def __new__(cls, x, y, alg=ns.DEFAULT, *args, **kwargs):
731681 try:
732 alg = _args_to_enum(**kwargs) | alg
682 alg = utils.args_to_enum(**kwargs) | alg
733683 except TypeError:
734 msg = ("natsort_keygen: 'alg' argument must be "
735 "from the enum 'ns'")
736 raise ValueError(msg + ', got {0}'.format(py23_str(alg)))
684 msg = "natsort_keygen: 'alg' argument must be " "from the enum 'ns'"
685 raise ValueError(msg + ", got {0}".format(py23_str(alg)))
737686
738687 # Add the _DUMB option if the locale library is broken.
739688 if alg & ns.LOCALEALPHA and natsort.compat.locale.dumb_sort():
740 alg |= ns._DUMB
689 alg |= ns_DUMB
741690
742691 if alg not in cls.cached_keys:
743692 cls.cached_keys[alg] = natsort_keygen(alg=alg)
00 # -*- coding: utf-8 -*-
1 """This module defines the "ns" enum for natsort."""
2 from __future__ import (
3 print_function,
4 division,
5 unicode_literals,
6 absolute_import
7 )
8
9
10 class ns(object):
1 """
2 This module defines the "ns" enum for natsort is used to determine
3 what algorithm natsort uses.
4 """
5 from __future__ import absolute_import, division, print_function, unicode_literals
6
7 import collections
8
9 # NOTE: OrderedDict is not used below for compatibility with Python 2.6.
10
11 # The below are the base ns options. The values will be stored as powers
12 # of two so bitmasks can be used to extract the user's requested options.
13 enum_options = [
14 "FLOAT",
15 "SIGNED",
16 "NOEXP",
17 "PATH",
18 "LOCALEALPHA",
19 "LOCALENUM",
20 "IGNORECASE",
21 "LOWERCASEFIRST",
22 "GROUPLETTERS",
23 "UNGROUPLETTERS",
24 "NANLAST",
25 "COMPATIBILITYNORMALIZE",
26 "NUMAFTER",
27 ]
28
29 # Following were previously options but are now defaults.
30 enum_do_nothing = ["DEFAULT", "TYPESAFE", "INT", "VERSION", "DIGIT", "UNSIGNED"]
31
32 # The following are bitwise-OR combinations of other fields.
33 enum_combos = [("REAL", ("FLOAT", "SIGNED")), ("LOCALE", ("LOCALEALPHA", "LOCALENUM"))]
34
35 # The following are aliases for other fields.
36 enum_aliases = [
37 ("T", "TYPESAFE"),
38 ("I", "INT"),
39 ("V", "VERSION"),
40 ("D", "DIGIT"),
41 ("U", "UNSIGNED"),
42 ("F", "FLOAT"),
43 ("S", "SIGNED"),
44 ("R", "REAL"),
45 ("N", "NOEXP"),
46 ("P", "PATH"),
47 ("LA", "LOCALEALPHA"),
48 ("LN", "LOCALENUM"),
49 ("L", "LOCALE"),
50 ("IC", "IGNORECASE"),
51 ("LF", "LOWERCASEFIRST"),
52 ("G", "GROUPLETTERS"),
53 ("UG", "UNGROUPLETTERS"),
54 ("C", "UNGROUPLETTERS"),
55 ("CAPITALFIRST", "UNGROUPLETTERS"),
56 ("NL", "NANLAST"),
57 ("CN", "COMPATIBILITYNORMALIZE"),
58 ("NA", "NUMAFTER"),
59 ]
60
61 # Construct the list of bitwise distinct enums with their fields.
62 enum_fields = [(name, 1 << i) for i, name in enumerate(enum_options)]
63 enum_fields.extend((name, 0) for name in enum_do_nothing)
64
65 for name, combo in enum_combos:
66 current_mapping = dict(enum_fields)
67 combined_value = current_mapping[combo[0]]
68 for combo_name in combo[1:]:
69 combined_value |= current_mapping[combo_name]
70 enum_fields.append((name, combined_value))
71
72 current_mapping = dict(enum_fields)
73 enum_fields.extend((alias, current_mapping[name]) for alias, name in enum_aliases)
74
75 # Finally, extract out the enum field names and their values.
76 enum_field_names, enum_field_values = zip(*enum_fields)
77
78
79 # Subclass the namedtuple to improve the docstring.
80 # noinspection PyUnresolvedReferences
81 class _NSEnum(collections.namedtuple("_NSEnum", enum_field_names)):
1182 """
1283 Enum to control the `natsort` algorithm.
1384
129200 True
130201
131202 """
132 # Following were previously now options but are now defaults.
133 TYPESAFE = T = 0
134 INT = I = 0
135 VERSION = V = 0
136 DIGIT = D = 0
137 UNSIGNED = U = 0
138
139 # The below are options. The values are stored as powers of two
140 # so bitmasks can be used to extract the user's requested options.
141 FLOAT = F = 1 << 0
142 SIGNED = S = 1 << 1
143 REAL = R = FLOAT | SIGNED
144 NOEXP = N = 1 << 2
145 PATH = P = 1 << 3
146 LOCALEALPHA = LA = 1 << 4
147 LOCALENUM = LN = 1 << 5
148 LOCALE = L = LOCALEALPHA | LOCALENUM
149 IGNORECASE = IC = 1 << 6
150 LOWERCASEFIRST = LF = 1 << 7
151 GROUPLETTERS = G = 1 << 8
152 UNGROUPLETTERS = UG = 1 << 9
153 CAPITALFIRST = C = UNGROUPLETTERS
154 NANLAST = NL = 1 << 10
155 COMPATIBILITYNORMALIZE = CN = 1 << 11
156 NUMAFTER = NA = 1 << 12
157
158 # The below are private options for internal use only.
159 _NUMERIC_ONLY = REAL | NOEXP
160 _DUMB = 1 << 31
203
204
205 # Here is where the instance of the ns enum that will be exported is created.
206 # It is a poor-man's singleton.
207 ns = _NSEnum(*enum_field_values)
208
209 # The below is private for internal use only.
210 ns_DUMB = 1 << 31
00 # -*- coding: utf-8 -*-
11 """
2 Contains all possible non-ASCII unicode numbers.
2 Pre-determine the collection of unicode decimals, digits, and numerals.
33 """
4 from __future__ import (
5 print_function,
6 division,
7 unicode_literals,
8 absolute_import
9 )
4 from __future__ import absolute_import, division, print_function, unicode_literals
105
11 # Std. lib imports.
126 import unicodedata
137
14 # Local imports.
158 from natsort.compat.py23 import py23_unichr
16
17
18 # Rather than determine this on the fly, which would incur a startup
19 # runtime penalty, the hex values of the Unicode numeric characters
20 # are hard-coded below.
21 numeric_hex = (
22 0XB2, 0XB3, 0XB9, 0XBC, 0XBD, 0XBE, 0X660, 0X661, 0X662,
23 0X663, 0X664, 0X665, 0X666, 0X667, 0X668, 0X669, 0X6F0,
24 0X6F1, 0X6F2, 0X6F3, 0X6F4, 0X6F5, 0X6F6, 0X6F7, 0X6F8,
25 0X6F9, 0X7C0, 0X7C1, 0X7C2, 0X7C3, 0X7C4, 0X7C5, 0X7C6,
26 0X7C7, 0X7C8, 0X7C9, 0X966, 0X967, 0X968, 0X969, 0X96A,
27 0X96B, 0X96C, 0X96D, 0X96E, 0X96F, 0X9E6, 0X9E7, 0X9E8,
28 0X9E9, 0X9EA, 0X9EB, 0X9EC, 0X9ED, 0X9EE, 0X9EF, 0X9F4,
29 0X9F5, 0X9F6, 0X9F7, 0X9F8, 0X9F9, 0XA66, 0XA67, 0XA68,
30 0XA69, 0XA6A, 0XA6B, 0XA6C, 0XA6D, 0XA6E, 0XA6F, 0XAE6,
31 0XAE7, 0XAE8, 0XAE9, 0XAEA, 0XAEB, 0XAEC, 0XAED, 0XAEE,
32 0XAEF, 0XB66, 0XB67, 0XB68, 0XB69, 0XB6A, 0XB6B, 0XB6C,
33 0XB6D, 0XB6E, 0XB6F, 0XB72, 0XB73, 0XB74, 0XB75, 0XB76,
34 0XB77, 0XBE6, 0XBE7, 0XBE8, 0XBE9, 0XBEA, 0XBEB, 0XBEC,
35 0XBED, 0XBEE, 0XBEF, 0XBF0, 0XBF1, 0XBF2, 0XC66, 0XC67,
36 0XC68, 0XC69, 0XC6A, 0XC6B, 0XC6C, 0XC6D, 0XC6E, 0XC6F,
37 0XC78, 0XC79, 0XC7A, 0XC7B, 0XC7C, 0XC7D, 0XC7E, 0XCE6,
38 0XCE7, 0XCE8, 0XCE9, 0XCEA, 0XCEB, 0XCEC, 0XCED, 0XCEE,
39 0XCEF, 0XD58, 0XD59, 0XD5A, 0XD5B, 0XD5C, 0XD5D, 0XD5E,
40 0XD66, 0XD67, 0XD68, 0XD69, 0XD6A, 0XD6B, 0XD6C, 0XD6D,
41 0XD6E, 0XD6F, 0XD70, 0XD71, 0XD72, 0XD73, 0XD74, 0XD75,
42 0XD76, 0XD77, 0XD78, 0XDE6, 0XDE7, 0XDE8, 0XDE9, 0XDEA,
43 0XDEB, 0XDEC, 0XDED, 0XDEE, 0XDEF, 0XE50, 0XE51, 0XE52,
44 0XE53, 0XE54, 0XE55, 0XE56, 0XE57, 0XE58, 0XE59, 0XED0,
45 0XED1, 0XED2, 0XED3, 0XED4, 0XED5, 0XED6, 0XED7, 0XED8,
46 0XED9, 0XF20, 0XF21, 0XF22, 0XF23, 0XF24, 0XF25, 0XF26,
47 0XF27, 0XF28, 0XF29, 0XF2A, 0XF2B, 0XF2C, 0XF2D, 0XF2E,
48 0XF2F, 0XF30, 0XF31, 0XF32, 0XF33, 0X1040, 0X1041, 0X1042,
49 0X1043, 0X1044, 0X1045, 0X1046, 0X1047, 0X1048, 0X1049,
50 0X1090, 0X1091, 0X1092, 0X1093, 0X1094, 0X1095, 0X1096,
51 0X1097, 0X1098, 0X1099, 0X1369, 0X136A, 0X136B, 0X136C,
52 0X136D, 0X136E, 0X136F, 0X1370, 0X1371, 0X1372, 0X1373,
53 0X1374, 0X1375, 0X1376, 0X1377, 0X1378, 0X1379, 0X137A,
54 0X137B, 0X137C, 0X16EE, 0X16EF, 0X16F0, 0X17E0, 0X17E1,
55 0X17E2, 0X17E3, 0X17E4, 0X17E5, 0X17E6, 0X17E7, 0X17E8,
56 0X17E9, 0X17F0, 0X17F1, 0X17F2, 0X17F3, 0X17F4, 0X17F5,
57 0X17F6, 0X17F7, 0X17F8, 0X17F9, 0X1810, 0X1811, 0X1812,
58 0X1813, 0X1814, 0X1815, 0X1816, 0X1817, 0X1818, 0X1819,
59 0X1946, 0X1947, 0X1948, 0X1949, 0X194A, 0X194B, 0X194C,
60 0X194D, 0X194E, 0X194F, 0X19D0, 0X19D1, 0X19D2, 0X19D3,
61 0X19D4, 0X19D5, 0X19D6, 0X19D7, 0X19D8, 0X19D9, 0X19DA,
62 0X1A80, 0X1A81, 0X1A82, 0X1A83, 0X1A84, 0X1A85, 0X1A86,
63 0X1A87, 0X1A88, 0X1A89, 0X1A90, 0X1A91, 0X1A92, 0X1A93,
64 0X1A94, 0X1A95, 0X1A96, 0X1A97, 0X1A98, 0X1A99, 0X1B50,
65 0X1B51, 0X1B52, 0X1B53, 0X1B54, 0X1B55, 0X1B56, 0X1B57,
66 0X1B58, 0X1B59, 0X1BB0, 0X1BB1, 0X1BB2, 0X1BB3, 0X1BB4,
67 0X1BB5, 0X1BB6, 0X1BB7, 0X1BB8, 0X1BB9, 0X1C40, 0X1C41,
68 0X1C42, 0X1C43, 0X1C44, 0X1C45, 0X1C46, 0X1C47, 0X1C48,
69 0X1C49, 0X1C50, 0X1C51, 0X1C52, 0X1C53, 0X1C54, 0X1C55,
70 0X1C56, 0X1C57, 0X1C58, 0X1C59, 0X2070, 0X2074, 0X2075,
71 0X2076, 0X2077, 0X2078, 0X2079, 0X2080, 0X2081, 0X2082,
72 0X2083, 0X2084, 0X2085, 0X2086, 0X2087, 0X2088, 0X2089,
73 0X2150, 0X2151, 0X2152, 0X2153, 0X2154, 0X2155, 0X2156,
74 0X2157, 0X2158, 0X2159, 0X215A, 0X215B, 0X215C, 0X215D,
75 0X215E, 0X215F, 0X2160, 0X2161, 0X2162, 0X2163, 0X2164,
76 0X2165, 0X2166, 0X2167, 0X2168, 0X2169, 0X216A, 0X216B,
77 0X216C, 0X216D, 0X216E, 0X216F, 0X2170, 0X2171, 0X2172,
78 0X2173, 0X2174, 0X2175, 0X2176, 0X2177, 0X2178, 0X2179,
79 0X217A, 0X217B, 0X217C, 0X217D, 0X217E, 0X217F, 0X2180,
80 0X2181, 0X2182, 0X2185, 0X2186, 0X2187, 0X2188, 0X2189,
81 0X2460, 0X2461, 0X2462, 0X2463, 0X2464, 0X2465, 0X2466,
82 0X2467, 0X2468, 0X2469, 0X246A, 0X246B, 0X246C, 0X246D,
83 0X246E, 0X246F, 0X2470, 0X2471, 0X2472, 0X2473, 0X2474,
84 0X2475, 0X2476, 0X2477, 0X2478, 0X2479, 0X247A, 0X247B,
85 0X247C, 0X247D, 0X247E, 0X247F, 0X2480, 0X2481, 0X2482,
86 0X2483, 0X2484, 0X2485, 0X2486, 0X2487, 0X2488, 0X2489,
87 0X248A, 0X248B, 0X248C, 0X248D, 0X248E, 0X248F, 0X2490,
88 0X2491, 0X2492, 0X2493, 0X2494, 0X2495, 0X2496, 0X2497,
89 0X2498, 0X2499, 0X249A, 0X249B, 0X24EA, 0X24EB, 0X24EC,
90 0X24ED, 0X24EE, 0X24EF, 0X24F0, 0X24F1, 0X24F2, 0X24F3,
91 0X24F4, 0X24F5, 0X24F6, 0X24F7, 0X24F8, 0X24F9, 0X24FA,
92 0X24FB, 0X24FC, 0X24FD, 0X24FE, 0X24FF, 0X2776, 0X2777,
93 0X2778, 0X2779, 0X277A, 0X277B, 0X277C, 0X277D, 0X277E,
94 0X277F, 0X2780, 0X2781, 0X2782, 0X2783, 0X2784, 0X2785,
95 0X2786, 0X2787, 0X2788, 0X2789, 0X278A, 0X278B, 0X278C,
96 0X278D, 0X278E, 0X278F, 0X2790, 0X2791, 0X2792, 0X2793,
97 0X2CFD, 0X3007, 0X3021, 0X3022, 0X3023, 0X3024, 0X3025,
98 0X3026, 0X3027, 0X3028, 0X3029, 0X3038, 0X3039, 0X303A,
99 0X3192, 0X3193, 0X3194, 0X3195, 0X3220, 0X3221, 0X3222,
100 0X3223, 0X3224, 0X3225, 0X3226, 0X3227, 0X3228, 0X3229,
101 0X3248, 0X3249, 0X324A, 0X324B, 0X324C, 0X324D, 0X324E,
102 0X324F, 0X3251, 0X3252, 0X3253, 0X3254, 0X3255, 0X3256,
103 0X3257, 0X3258, 0X3259, 0X325A, 0X325B, 0X325C, 0X325D,
104 0X325E, 0X325F, 0X3280, 0X3281, 0X3282, 0X3283, 0X3284,
105 0X3285, 0X3286, 0X3287, 0X3288, 0X3289, 0X32B1, 0X32B2,
106 0X32B3, 0X32B4, 0X32B5, 0X32B6, 0X32B7, 0X32B8, 0X32B9,
107 0X32BA, 0X32BB, 0X32BC, 0X32BD, 0X32BE, 0X32BF, 0X3405,
108 0X3483, 0X382A, 0X3B4D, 0X4E00, 0X4E03, 0X4E07, 0X4E09,
109 0X4E5D, 0X4E8C, 0X4E94, 0X4E96, 0X4EBF, 0X4EC0, 0X4EDF,
110 0X4EE8, 0X4F0D, 0X4F70, 0X5104, 0X5146, 0X5169, 0X516B,
111 0X516D, 0X5341, 0X5343, 0X5344, 0X5345, 0X534C, 0X53C1,
112 0X53C2, 0X53C3, 0X53C4, 0X56DB, 0X58F1, 0X58F9, 0X5E7A,
113 0X5EFE, 0X5EFF, 0X5F0C, 0X5F0D, 0X5F0E, 0X5F10, 0X62FE,
114 0X634C, 0X67D2, 0X6F06, 0X7396, 0X767E, 0X8086, 0X842C,
115 0X8CAE, 0X8CB3, 0X8D30, 0X9621, 0X9646, 0X964C, 0X9678,
116 0X96F6, 0XA620, 0XA621, 0XA622, 0XA623, 0XA624, 0XA625,
117 0XA626, 0XA627, 0XA628, 0XA629, 0XA6E6, 0XA6E7, 0XA6E8,
118 0XA6E9, 0XA6EA, 0XA6EB, 0XA6EC, 0XA6ED, 0XA6EE, 0XA6EF,
119 0XA830, 0XA831, 0XA832, 0XA833, 0XA834, 0XA835, 0XA8D0,
120 0XA8D1, 0XA8D2, 0XA8D3, 0XA8D4, 0XA8D5, 0XA8D6, 0XA8D7,
121 0XA8D8, 0XA8D9, 0XA900, 0XA901, 0XA902, 0XA903, 0XA904,
122 0XA905, 0XA906, 0XA907, 0XA908, 0XA909, 0XA9D0, 0XA9D1,
123 0XA9D2, 0XA9D3, 0XA9D4, 0XA9D5, 0XA9D6, 0XA9D7, 0XA9D8,
124 0XA9D9, 0XA9F0, 0XA9F1, 0XA9F2, 0XA9F3, 0XA9F4, 0XA9F5,
125 0XA9F6, 0XA9F7, 0XA9F8, 0XA9F9, 0XAA50, 0XAA51, 0XAA52,
126 0XAA53, 0XAA54, 0XAA55, 0XAA56, 0XAA57, 0XAA58, 0XAA59,
127 0XABF0, 0XABF1, 0XABF2, 0XABF3, 0XABF4, 0XABF5, 0XABF6,
128 0XABF7, 0XABF8, 0XABF9, 0XF96B, 0XF973, 0XF978, 0XF9B2,
129 0XF9D1, 0XF9D3, 0XF9FD, 0XFF10, 0XFF11, 0XFF12, 0XFF13,
130 0XFF14, 0XFF15, 0XFF16, 0XFF17, 0XFF18, 0XFF19, 0X10107,
131 0X10108, 0X10109, 0X1010A, 0X1010B, 0X1010C, 0X1010D,
132 0X1010E, 0X1010F, 0X10110, 0X10111, 0X10112, 0X10113,
133 0X10114, 0X10115, 0X10116, 0X10117, 0X10118, 0X10119,
134 0X1011A, 0X1011B, 0X1011C, 0X1011D, 0X1011E, 0X1011F,
135 0X10120, 0X10121, 0X10122, 0X10123, 0X10124, 0X10125,
136 0X10126, 0X10127, 0X10128, 0X10129, 0X1012A, 0X1012B,
137 0X1012C, 0X1012D, 0X1012E, 0X1012F, 0X10130, 0X10131,
138 0X10132, 0X10133, 0X10140, 0X10141, 0X10142, 0X10143,
139 0X10144, 0X10145, 0X10146, 0X10147, 0X10148, 0X10149,
140 0X1014A, 0X1014B, 0X1014C, 0X1014D, 0X1014E, 0X1014F,
141 0X10150, 0X10151, 0X10152, 0X10153, 0X10154, 0X10155,
142 0X10156, 0X10157, 0X10158, 0X10159, 0X1015A, 0X1015B,
143 0X1015C, 0X1015D, 0X1015E, 0X1015F, 0X10160, 0X10161,
144 0X10162, 0X10163, 0X10164, 0X10165, 0X10166, 0X10167,
145 0X10168, 0X10169, 0X1016A, 0X1016B, 0X1016C, 0X1016D,
146 0X1016E, 0X1016F, 0X10170, 0X10171, 0X10172, 0X10173,
147 0X10174, 0X10175, 0X10176, 0X10177, 0X10178, 0X1018A,
148 0X1018B, 0X102E1, 0X102E2, 0X102E3, 0X102E4, 0X102E5,
149 0X102E6, 0X102E7, 0X102E8, 0X102E9, 0X102EA, 0X102EB,
150 0X102EC, 0X102ED, 0X102EE, 0X102EF, 0X102F0, 0X102F1,
151 0X102F2, 0X102F3, 0X102F4, 0X102F5, 0X102F6, 0X102F7,
152 0X102F8, 0X102F9, 0X102FA, 0X102FB, 0X10320, 0X10321,
153 0X10322, 0X10323, 0X10341, 0X1034A, 0X103D1, 0X103D2,
154 0X103D3, 0X103D4, 0X103D5, 0X104A0, 0X104A1, 0X104A2,
155 0X104A3, 0X104A4, 0X104A5, 0X104A6, 0X104A7, 0X104A8,
156 0X104A9, 0X10858, 0X10859, 0X1085A, 0X1085B, 0X1085C,
157 0X1085D, 0X1085E, 0X1085F, 0X10879, 0X1087A, 0X1087B,
158 0X1087C, 0X1087D, 0X1087E, 0X1087F, 0X108A7, 0X108A8,
159 0X108A9, 0X108AA, 0X108AB, 0X108AC, 0X108AD, 0X108AE,
160 0X108AF, 0X108FB, 0X108FC, 0X108FD, 0X108FE, 0X108FF,
161 0X10916, 0X10917, 0X10918, 0X10919, 0X1091A, 0X1091B,
162 0X109BC, 0X109BD, 0X109C0, 0X109C1, 0X109C2, 0X109C3,
163 0X109C4, 0X109C5, 0X109C6, 0X109C7, 0X109C8, 0X109C9,
164 0X109CA, 0X109CB, 0X109CC, 0X109CD, 0X109CE, 0X109CF,
165 0X109D2, 0X109D3, 0X109D4, 0X109D5, 0X109D6, 0X109D7,
166 0X109D8, 0X109D9, 0X109DA, 0X109DB, 0X109DC, 0X109DD,
167 0X109DE, 0X109DF, 0X109E0, 0X109E1, 0X109E2, 0X109E3,
168 0X109E4, 0X109E5, 0X109E6, 0X109E7, 0X109E8, 0X109E9,
169 0X109EA, 0X109EB, 0X109EC, 0X109ED, 0X109EE, 0X109EF,
170 0X109F0, 0X109F1, 0X109F2, 0X109F3, 0X109F4, 0X109F5,
171 0X109F6, 0X109F7, 0X109F8, 0X109F9, 0X109FA, 0X109FB,
172 0X109FC, 0X109FD, 0X109FE, 0X109FF, 0X10A40, 0X10A41,
173 0X10A42, 0X10A43, 0X10A44, 0X10A45, 0X10A46, 0X10A47,
174 0X10A48, 0X10A7D, 0X10A7E, 0X10A9D, 0X10A9E, 0X10A9F,
175 0X10AEB, 0X10AEC, 0X10AED, 0X10AEE, 0X10AEF, 0X10B58,
176 0X10B59, 0X10B5A, 0X10B5B, 0X10B5C, 0X10B5D, 0X10B5E,
177 0X10B5F, 0X10B78, 0X10B79, 0X10B7A, 0X10B7B, 0X10B7C,
178 0X10B7D, 0X10B7E, 0X10B7F, 0X10BA9, 0X10BAA, 0X10BAB,
179 0X10BAC, 0X10BAD, 0X10BAE, 0X10BAF, 0X10CFA, 0X10CFB,
180 0X10CFC, 0X10CFD, 0X10CFE, 0X10CFF, 0X10D30, 0X10D31,
181 0X10D32, 0X10D33, 0X10D34, 0X10D35, 0X10D36, 0X10D37,
182 0X10D38, 0X10D39, 0X10E60, 0X10E61, 0X10E62, 0X10E63,
183 0X10E64, 0X10E65, 0X10E66, 0X10E67, 0X10E68, 0X10E69,
184 0X10E6A, 0X10E6B, 0X10E6C, 0X10E6D, 0X10E6E, 0X10E6F,
185 0X10E70, 0X10E71, 0X10E72, 0X10E73, 0X10E74, 0X10E75,
186 0X10E76, 0X10E77, 0X10E78, 0X10E79, 0X10E7A, 0X10E7B,
187 0X10E7C, 0X10E7D, 0X10E7E, 0X10F1D, 0X10F1E, 0X10F1F,
188 0X10F20, 0X10F21, 0X10F22, 0X10F23, 0X10F24, 0X10F25,
189 0X10F26, 0X10F51, 0X10F52, 0X10F53, 0X10F54, 0X11052,
190 0X11053, 0X11054, 0X11055, 0X11056, 0X11057, 0X11058,
191 0X11059, 0X1105A, 0X1105B, 0X1105C, 0X1105D, 0X1105E,
192 0X1105F, 0X11060, 0X11061, 0X11062, 0X11063, 0X11064,
193 0X11065, 0X11066, 0X11067, 0X11068, 0X11069, 0X1106A,
194 0X1106B, 0X1106C, 0X1106D, 0X1106E, 0X1106F, 0X110F0,
195 0X110F1, 0X110F2, 0X110F3, 0X110F4, 0X110F5, 0X110F6,
196 0X110F7, 0X110F8, 0X110F9, 0X11136, 0X11137, 0X11138,
197 0X11139, 0X1113A, 0X1113B, 0X1113C, 0X1113D, 0X1113E,
198 0X1113F, 0X111D0, 0X111D1, 0X111D2, 0X111D3, 0X111D4,
199 0X111D5, 0X111D6, 0X111D7, 0X111D8, 0X111D9, 0X111E1,
200 0X111E2, 0X111E3, 0X111E4, 0X111E5, 0X111E6, 0X111E7,
201 0X111E8, 0X111E9, 0X111EA, 0X111EB, 0X111EC, 0X111ED,
202 0X111EE, 0X111EF, 0X111F0, 0X111F1, 0X111F2, 0X111F3,
203 0X111F4, 0X112F0, 0X112F1, 0X112F2, 0X112F3, 0X112F4,
204 0X112F5, 0X112F6, 0X112F7, 0X112F8, 0X112F9, 0X11450,
205 0X11451, 0X11452, 0X11453, 0X11454, 0X11455, 0X11456,
206 0X11457, 0X11458, 0X11459, 0X114D0, 0X114D1, 0X114D2,
207 0X114D3, 0X114D4, 0X114D5, 0X114D6, 0X114D7, 0X114D8,
208 0X114D9, 0X11650, 0X11651, 0X11652, 0X11653, 0X11654,
209 0X11655, 0X11656, 0X11657, 0X11658, 0X11659, 0X116C0,
210 0X116C1, 0X116C2, 0X116C3, 0X116C4, 0X116C5, 0X116C6,
211 0X116C7, 0X116C8, 0X116C9, 0X11730, 0X11731, 0X11732,
212 0X11733, 0X11734, 0X11735, 0X11736, 0X11737, 0X11738,
213 0X11739, 0X1173A, 0X1173B, 0X118E0, 0X118E1, 0X118E2,
214 0X118E3, 0X118E4, 0X118E5, 0X118E6, 0X118E7, 0X118E8,
215 0X118E9, 0X118EA, 0X118EB, 0X118EC, 0X118ED, 0X118EE,
216 0X118EF, 0X118F0, 0X118F1, 0X118F2, 0X11C50, 0X11C51,
217 0X11C52, 0X11C53, 0X11C54, 0X11C55, 0X11C56, 0X11C57,
218 0X11C58, 0X11C59, 0X11C5A, 0X11C5B, 0X11C5C, 0X11C5D,
219 0X11C5E, 0X11C5F, 0X11C60, 0X11C61, 0X11C62, 0X11C63,
220 0X11C64, 0X11C65, 0X11C66, 0X11C67, 0X11C68, 0X11C69,
221 0X11C6A, 0X11C6B, 0X11C6C, 0X11D50, 0X11D51, 0X11D52,
222 0X11D53, 0X11D54, 0X11D55, 0X11D56, 0X11D57, 0X11D58,
223 0X11D59, 0X11DA0, 0X11DA1, 0X11DA2, 0X11DA3, 0X11DA4,
224 0X11DA5, 0X11DA6, 0X11DA7, 0X11DA8, 0X11DA9, 0X12400,
225 0X12401, 0X12402, 0X12403, 0X12404, 0X12405, 0X12406,
226 0X12407, 0X12408, 0X12409, 0X1240A, 0X1240B, 0X1240C,
227 0X1240D, 0X1240E, 0X1240F, 0X12410, 0X12411, 0X12412,
228 0X12413, 0X12414, 0X12415, 0X12416, 0X12417, 0X12418,
229 0X12419, 0X1241A, 0X1241B, 0X1241C, 0X1241D, 0X1241E,
230 0X1241F, 0X12420, 0X12421, 0X12422, 0X12423, 0X12424,
231 0X12425, 0X12426, 0X12427, 0X12428, 0X12429, 0X1242A,
232 0X1242B, 0X1242C, 0X1242D, 0X1242E, 0X1242F, 0X12430,
233 0X12431, 0X12432, 0X12433, 0X12434, 0X12435, 0X12436,
234 0X12437, 0X12438, 0X12439, 0X1243A, 0X1243B, 0X1243C,
235 0X1243D, 0X1243E, 0X1243F, 0X12440, 0X12441, 0X12442,
236 0X12443, 0X12444, 0X12445, 0X12446, 0X12447, 0X12448,
237 0X12449, 0X1244A, 0X1244B, 0X1244C, 0X1244D, 0X1244E,
238 0X1244F, 0X12450, 0X12451, 0X12452, 0X12453, 0X12454,
239 0X12455, 0X12456, 0X12457, 0X12458, 0X12459, 0X1245A,
240 0X1245B, 0X1245C, 0X1245D, 0X1245E, 0X1245F, 0X12460,
241 0X12461, 0X12462, 0X12463, 0X12464, 0X12465, 0X12466,
242 0X12467, 0X12468, 0X12469, 0X1246A, 0X1246B, 0X1246C,
243 0X1246D, 0X1246E, 0X16A60, 0X16A61, 0X16A62, 0X16A63,
244 0X16A64, 0X16A65, 0X16A66, 0X16A67, 0X16A68, 0X16A69,
245 0X16B50, 0X16B51, 0X16B52, 0X16B53, 0X16B54, 0X16B55,
246 0X16B56, 0X16B57, 0X16B58, 0X16B59, 0X16B5B, 0X16B5C,
247 0X16B5D, 0X16B5E, 0X16B5F, 0X16B60, 0X16B61, 0X16E80,
248 0X16E81, 0X16E82, 0X16E83, 0X16E84, 0X16E85, 0X16E86,
249 0X16E87, 0X16E88, 0X16E89, 0X16E8A, 0X16E8B, 0X16E8C,
250 0X16E8D, 0X16E8E, 0X16E8F, 0X16E90, 0X16E91, 0X16E92,
251 0X16E93, 0X16E94, 0X16E95, 0X16E96, 0X1D2E0, 0X1D2E1,
252 0X1D2E2, 0X1D2E3, 0X1D2E4, 0X1D2E5, 0X1D2E6, 0X1D2E7,
253 0X1D2E8, 0X1D2E9, 0X1D2EA, 0X1D2EB, 0X1D2EC, 0X1D2ED,
254 0X1D2EE, 0X1D2EF, 0X1D2F0, 0X1D2F1, 0X1D2F2, 0X1D2F3,
255 0X1D360, 0X1D361, 0X1D362, 0X1D363, 0X1D364, 0X1D365,
256 0X1D366, 0X1D367, 0X1D368, 0X1D369, 0X1D36A, 0X1D36B,
257 0X1D36C, 0X1D36D, 0X1D36E, 0X1D36F, 0X1D370, 0X1D371,
258 0X1D372, 0X1D373, 0X1D374, 0X1D375, 0X1D376, 0X1D377,
259 0X1D378, 0X1D7CE, 0X1D7CF, 0X1D7D0, 0X1D7D1, 0X1D7D2,
260 0X1D7D3, 0X1D7D4, 0X1D7D5, 0X1D7D6, 0X1D7D7, 0X1D7D8,
261 0X1D7D9, 0X1D7DA, 0X1D7DB, 0X1D7DC, 0X1D7DD, 0X1D7DE,
262 0X1D7DF, 0X1D7E0, 0X1D7E1, 0X1D7E2, 0X1D7E3, 0X1D7E4,
263 0X1D7E5, 0X1D7E6, 0X1D7E7, 0X1D7E8, 0X1D7E9, 0X1D7EA,
264 0X1D7EB, 0X1D7EC, 0X1D7ED, 0X1D7EE, 0X1D7EF, 0X1D7F0,
265 0X1D7F1, 0X1D7F2, 0X1D7F3, 0X1D7F4, 0X1D7F5, 0X1D7F6,
266 0X1D7F7, 0X1D7F8, 0X1D7F9, 0X1D7FA, 0X1D7FB, 0X1D7FC,
267 0X1D7FD, 0X1D7FE, 0X1D7FF, 0X1E8C7, 0X1E8C8, 0X1E8C9,
268 0X1E8CA, 0X1E8CB, 0X1E8CC, 0X1E8CD, 0X1E8CE, 0X1E8CF,
269 0X1E950, 0X1E951, 0X1E952, 0X1E953, 0X1E954, 0X1E955,
270 0X1E956, 0X1E957, 0X1E958, 0X1E959, 0X1EC71, 0X1EC72,
271 0X1EC73, 0X1EC74, 0X1EC75, 0X1EC76, 0X1EC77, 0X1EC78,
272 0X1EC79, 0X1EC7A, 0X1EC7B, 0X1EC7C, 0X1EC7D, 0X1EC7E,
273 0X1EC7F, 0X1EC80, 0X1EC81, 0X1EC82, 0X1EC83, 0X1EC84,
274 0X1EC85, 0X1EC86, 0X1EC87, 0X1EC88, 0X1EC89, 0X1EC8A,
275 0X1EC8B, 0X1EC8C, 0X1EC8D, 0X1EC8E, 0X1EC8F, 0X1EC90,
276 0X1EC91, 0X1EC92, 0X1EC93, 0X1EC94, 0X1EC95, 0X1EC96,
277 0X1EC97, 0X1EC98, 0X1EC99, 0X1EC9A, 0X1EC9B, 0X1EC9C,
278 0X1EC9D, 0X1EC9E, 0X1EC9F, 0X1ECA0, 0X1ECA1, 0X1ECA2,
279 0X1ECA3, 0X1ECA4, 0X1ECA5, 0X1ECA6, 0X1ECA7, 0X1ECA8,
280 0X1ECA9, 0X1ECAA, 0X1ECAB, 0X1ECAD, 0X1ECAE, 0X1ECAF,
281 0X1ECB1, 0X1ECB2, 0X1ECB3, 0X1ECB4, 0X1F100, 0X1F101,
282 0X1F102, 0X1F103, 0X1F104, 0X1F105, 0X1F106, 0X1F107,
283 0X1F108, 0X1F109, 0X1F10A, 0X1F10B, 0X1F10C, 0X20001,
284 0X20064, 0X200E2, 0X20121, 0X2092A, 0X20983, 0X2098C,
285 0X2099C, 0X20AEA, 0X20AFD, 0X20B19, 0X22390, 0X22998,
286 0X23B1B, 0X2626D, 0X2F890,
287 )
9 from natsort.unicode_numeric_hex import numeric_hex
28810
28911 # Convert each hex into the literal Unicode character.
29012 # Stop if a ValueError is raised in case of a narrow Unicode build.
29315 numeric_chars = []
29416 for a in numeric_hex:
29517 try:
296 l = py23_unichr(a)
18 character = py23_unichr(a)
29719 except ValueError: # pragma: no cover
29820 break
299 if unicodedata.numeric(l, None) is None:
21 if unicodedata.numeric(character, None) is None:
30022 continue # pragma: no cover
301 numeric_chars.append(l)
23 numeric_chars.append(character)
30224
30325 # The digit characters are a subset of the numerals.
304 digit_chars = [a for a in numeric_chars
305 if unicodedata.digit(a, None) is not None]
26 digit_chars = [a for a in numeric_chars if unicodedata.digit(a, None) is not None]
30627
30728 # The decimal characters are a subset of the numberals
30829 # (probably of the digits, but let's be safe).
309 decimal_chars = [a for a in numeric_chars
310 if unicodedata.decimal(a, None) is not None]
30 decimal_chars = [a for a in numeric_chars if unicodedata.decimal(a, None) is not None]
31131
31232 # Create a single string with the above data.
313 decimals = ''.join(decimal_chars)
314 digits = ''.join(digit_chars)
315 numeric = ''.join(numeric_chars)
316 digits_no_decimals = ''.join([x for x in digits if x not in decimals])
317 numeric_no_decimals = ''.join([x for x in numeric if x not in decimals])
318
319 # Some code that can be used to create the above list of hex numbers.
320 if __name__ == '__main__':
321 import textwrap
322 from natsort.compat.py23 import py23_range
323
324 hex_chars = []
325 for i in py23_range(0X110000):
326 try:
327 a = py23_unichr(i)
328 except ValueError:
329 break
330 if a in set('0123456789'):
331 continue
332 if unicodedata.numeric(a, None) is not None:
333 hex_chars.append(i)
334
335 hex_string = ', '.join(['0X{:X}'.format(i) for i in hex_chars])
336 for line in textwrap.wrap(hex_string, width=60):
337 print(' ', line)
33 decimals = "".join(decimal_chars)
34 digits = "".join(digit_chars)
35 numeric = "".join(numeric_chars)
36 digits_no_decimals = "".join([x for x in digits if x not in decimals])
37 numeric_no_decimals = "".join([x for x in numeric if x not in decimals])
0 # -*- coding: utf-8 -*-
1 """
2 Contains all possible non-ASCII unicode numbers.
3 """
4
5 # Rather than determine what unicode characters are numeric on the fly which
6 # would incur a startup runtime penalty, the hex values are hard-coded below.
7 numeric_hex = (
8 0XB2,
9 0XB3,
10 0XB9,
11 0XBC,
12 0XBD,
13 0XBE,
14 0X660,
15 0X661,
16 0X662,
17 0X663,
18 0X664,
19 0X665,
20 0X666,
21 0X667,
22 0X668,
23 0X669,
24 0X6F0,
25 0X6F1,
26 0X6F2,
27 0X6F3,
28 0X6F4,
29 0X6F5,
30 0X6F6,
31 0X6F7,
32 0X6F8,
33 0X6F9,
34 0X7C0,
35 0X7C1,
36 0X7C2,
37 0X7C3,
38 0X7C4,
39 0X7C5,
40 0X7C6,
41 0X7C7,
42 0X7C8,
43 0X7C9,
44 0X966,
45 0X967,
46 0X968,
47 0X969,
48 0X96A,
49 0X96B,
50 0X96C,
51 0X96D,
52 0X96E,
53 0X96F,
54 0X9E6,
55 0X9E7,
56 0X9E8,
57 0X9E9,
58 0X9EA,
59 0X9EB,
60 0X9EC,
61 0X9ED,
62 0X9EE,
63 0X9EF,
64 0X9F4,
65 0X9F5,
66 0X9F6,
67 0X9F7,
68 0X9F8,
69 0X9F9,
70 0XA66,
71 0XA67,
72 0XA68,
73 0XA69,
74 0XA6A,
75 0XA6B,
76 0XA6C,
77 0XA6D,
78 0XA6E,
79 0XA6F,
80 0XAE6,
81 0XAE7,
82 0XAE8,
83 0XAE9,
84 0XAEA,
85 0XAEB,
86 0XAEC,
87 0XAED,
88 0XAEE,
89 0XAEF,
90 0XB66,
91 0XB67,
92 0XB68,
93 0XB69,
94 0XB6A,
95 0XB6B,
96 0XB6C,
97 0XB6D,
98 0XB6E,
99 0XB6F,
100 0XB72,
101 0XB73,
102 0XB74,
103 0XB75,
104 0XB76,
105 0XB77,
106 0XBE6,
107 0XBE7,
108 0XBE8,
109 0XBE9,
110 0XBEA,
111 0XBEB,
112 0XBEC,
113 0XBED,
114 0XBEE,
115 0XBEF,
116 0XBF0,
117 0XBF1,
118 0XBF2,
119 0XC66,
120 0XC67,
121 0XC68,
122 0XC69,
123 0XC6A,
124 0XC6B,
125 0XC6C,
126 0XC6D,
127 0XC6E,
128 0XC6F,
129 0XC78,
130 0XC79,
131 0XC7A,
132 0XC7B,
133 0XC7C,
134 0XC7D,
135 0XC7E,
136 0XCE6,
137 0XCE7,
138 0XCE8,
139 0XCE9,
140 0XCEA,
141 0XCEB,
142 0XCEC,
143 0XCED,
144 0XCEE,
145 0XCEF,
146 0XD58,
147 0XD59,
148 0XD5A,
149 0XD5B,
150 0XD5C,
151 0XD5D,
152 0XD5E,
153 0XD66,
154 0XD67,
155 0XD68,
156 0XD69,
157 0XD6A,
158 0XD6B,
159 0XD6C,
160 0XD6D,
161 0XD6E,
162 0XD6F,
163 0XD70,
164 0XD71,
165 0XD72,
166 0XD73,
167 0XD74,
168 0XD75,
169 0XD76,
170 0XD77,
171 0XD78,
172 0XDE6,
173 0XDE7,
174 0XDE8,
175 0XDE9,
176 0XDEA,
177 0XDEB,
178 0XDEC,
179 0XDED,
180 0XDEE,
181 0XDEF,
182 0XE50,
183 0XE51,
184 0XE52,
185 0XE53,
186 0XE54,
187 0XE55,
188 0XE56,
189 0XE57,
190 0XE58,
191 0XE59,
192 0XED0,
193 0XED1,
194 0XED2,
195 0XED3,
196 0XED4,
197 0XED5,
198 0XED6,
199 0XED7,
200 0XED8,
201 0XED9,
202 0XF20,
203 0XF21,
204 0XF22,
205 0XF23,
206 0XF24,
207 0XF25,
208 0XF26,
209 0XF27,
210 0XF28,
211 0XF29,
212 0XF2A,
213 0XF2B,
214 0XF2C,
215 0XF2D,
216 0XF2E,
217 0XF2F,
218 0XF30,
219 0XF31,
220 0XF32,
221 0XF33,
222 0X1040,
223 0X1041,
224 0X1042,
225 0X1043,
226 0X1044,
227 0X1045,
228 0X1046,
229 0X1047,
230 0X1048,
231 0X1049,
232 0X1090,
233 0X1091,
234 0X1092,
235 0X1093,
236 0X1094,
237 0X1095,
238 0X1096,
239 0X1097,
240 0X1098,
241 0X1099,
242 0X1369,
243 0X136A,
244 0X136B,
245 0X136C,
246 0X136D,
247 0X136E,
248 0X136F,
249 0X1370,
250 0X1371,
251 0X1372,
252 0X1373,
253 0X1374,
254 0X1375,
255 0X1376,
256 0X1377,
257 0X1378,
258 0X1379,
259 0X137A,
260 0X137B,
261 0X137C,
262 0X16EE,
263 0X16EF,
264 0X16F0,
265 0X17E0,
266 0X17E1,
267 0X17E2,
268 0X17E3,
269 0X17E4,
270 0X17E5,
271 0X17E6,
272 0X17E7,
273 0X17E8,
274 0X17E9,
275 0X17F0,
276 0X17F1,
277 0X17F2,
278 0X17F3,
279 0X17F4,
280 0X17F5,
281 0X17F6,
282 0X17F7,
283 0X17F8,
284 0X17F9,
285 0X1810,
286 0X1811,
287 0X1812,
288 0X1813,
289 0X1814,
290 0X1815,
291 0X1816,
292 0X1817,
293 0X1818,
294 0X1819,
295 0X1946,
296 0X1947,
297 0X1948,
298 0X1949,
299 0X194A,
300 0X194B,
301 0X194C,
302 0X194D,
303 0X194E,
304 0X194F,
305 0X19D0,
306 0X19D1,
307 0X19D2,
308 0X19D3,
309 0X19D4,
310 0X19D5,
311 0X19D6,
312 0X19D7,
313 0X19D8,
314 0X19D9,
315 0X19DA,
316 0X1A80,
317 0X1A81,
318 0X1A82,
319 0X1A83,
320 0X1A84,
321 0X1A85,
322 0X1A86,
323 0X1A87,
324 0X1A88,
325 0X1A89,
326 0X1A90,
327 0X1A91,
328 0X1A92,
329 0X1A93,
330 0X1A94,
331 0X1A95,
332 0X1A96,
333 0X1A97,
334 0X1A98,
335 0X1A99,
336 0X1B50,
337 0X1B51,
338 0X1B52,
339 0X1B53,
340 0X1B54,
341 0X1B55,
342 0X1B56,
343 0X1B57,
344 0X1B58,
345 0X1B59,
346 0X1BB0,
347 0X1BB1,
348 0X1BB2,
349 0X1BB3,
350 0X1BB4,
351 0X1BB5,
352 0X1BB6,
353 0X1BB7,
354 0X1BB8,
355 0X1BB9,
356 0X1C40,
357 0X1C41,
358 0X1C42,
359 0X1C43,
360 0X1C44,
361 0X1C45,
362 0X1C46,
363 0X1C47,
364 0X1C48,
365 0X1C49,
366 0X1C50,
367 0X1C51,
368 0X1C52,
369 0X1C53,
370 0X1C54,
371 0X1C55,
372 0X1C56,
373 0X1C57,
374 0X1C58,
375 0X1C59,
376 0X2070,
377 0X2074,
378 0X2075,
379 0X2076,
380 0X2077,
381 0X2078,
382 0X2079,
383 0X2080,
384 0X2081,
385 0X2082,
386 0X2083,
387 0X2084,
388 0X2085,
389 0X2086,
390 0X2087,
391 0X2088,
392 0X2089,
393 0X2150,
394 0X2151,
395 0X2152,
396 0X2153,
397 0X2154,
398 0X2155,
399 0X2156,
400 0X2157,
401 0X2158,
402 0X2159,
403 0X215A,
404 0X215B,
405 0X215C,
406 0X215D,
407 0X215E,
408 0X215F,
409 0X2160,
410 0X2161,
411 0X2162,
412 0X2163,
413 0X2164,
414 0X2165,
415 0X2166,
416 0X2167,
417 0X2168,
418 0X2169,
419 0X216A,
420 0X216B,
421 0X216C,
422 0X216D,
423 0X216E,
424 0X216F,
425 0X2170,
426 0X2171,
427 0X2172,
428 0X2173,
429 0X2174,
430 0X2175,
431 0X2176,
432 0X2177,
433 0X2178,
434 0X2179,
435 0X217A,
436 0X217B,
437 0X217C,
438 0X217D,
439 0X217E,
440 0X217F,
441 0X2180,
442 0X2181,
443 0X2182,
444 0X2185,
445 0X2186,
446 0X2187,
447 0X2188,
448 0X2189,
449 0X2460,
450 0X2461,
451 0X2462,
452 0X2463,
453 0X2464,
454 0X2465,
455 0X2466,
456 0X2467,
457 0X2468,
458 0X2469,
459 0X246A,
460 0X246B,
461 0X246C,
462 0X246D,
463 0X246E,
464 0X246F,
465 0X2470,
466 0X2471,
467 0X2472,
468 0X2473,
469 0X2474,
470 0X2475,
471 0X2476,
472 0X2477,
473 0X2478,
474 0X2479,
475 0X247A,
476 0X247B,
477 0X247C,
478 0X247D,
479 0X247E,
480 0X247F,
481 0X2480,
482 0X2481,
483 0X2482,
484 0X2483,
485 0X2484,
486 0X2485,
487 0X2486,
488 0X2487,
489 0X2488,
490 0X2489,
491 0X248A,
492 0X248B,
493 0X248C,
494 0X248D,
495 0X248E,
496 0X248F,
497 0X2490,
498 0X2491,
499 0X2492,
500 0X2493,
501 0X2494,
502 0X2495,
503 0X2496,
504 0X2497,
505 0X2498,
506 0X2499,
507 0X249A,
508 0X249B,
509 0X24EA,
510 0X24EB,
511 0X24EC,
512 0X24ED,
513 0X24EE,
514 0X24EF,
515 0X24F0,
516 0X24F1,
517 0X24F2,
518 0X24F3,
519 0X24F4,
520 0X24F5,
521 0X24F6,
522 0X24F7,
523 0X24F8,
524 0X24F9,
525 0X24FA,
526 0X24FB,
527 0X24FC,
528 0X24FD,
529 0X24FE,
530 0X24FF,
531 0X2776,
532 0X2777,
533 0X2778,
534 0X2779,
535 0X277A,
536 0X277B,
537 0X277C,
538 0X277D,
539 0X277E,
540 0X277F,
541 0X2780,
542 0X2781,
543 0X2782,
544 0X2783,
545 0X2784,
546 0X2785,
547 0X2786,
548 0X2787,
549 0X2788,
550 0X2789,
551 0X278A,
552 0X278B,
553 0X278C,
554 0X278D,
555 0X278E,
556 0X278F,
557 0X2790,
558 0X2791,
559 0X2792,
560 0X2793,
561 0X2CFD,
562 0X3007,
563 0X3021,
564 0X3022,
565 0X3023,
566 0X3024,
567 0X3025,
568 0X3026,
569 0X3027,
570 0X3028,
571 0X3029,
572 0X3038,
573 0X3039,
574 0X303A,
575 0X3192,
576 0X3193,
577 0X3194,
578 0X3195,
579 0X3220,
580 0X3221,
581 0X3222,
582 0X3223,
583 0X3224,
584 0X3225,
585 0X3226,
586 0X3227,
587 0X3228,
588 0X3229,
589 0X3248,
590 0X3249,
591 0X324A,
592 0X324B,
593 0X324C,
594 0X324D,
595 0X324E,
596 0X324F,
597 0X3251,
598 0X3252,
599 0X3253,
600 0X3254,
601 0X3255,
602 0X3256,
603 0X3257,
604 0X3258,
605 0X3259,
606 0X325A,
607 0X325B,
608 0X325C,
609 0X325D,
610 0X325E,
611 0X325F,
612 0X3280,
613 0X3281,
614 0X3282,
615 0X3283,
616 0X3284,
617 0X3285,
618 0X3286,
619 0X3287,
620 0X3288,
621 0X3289,
622 0X32B1,
623 0X32B2,
624 0X32B3,
625 0X32B4,
626 0X32B5,
627 0X32B6,
628 0X32B7,
629 0X32B8,
630 0X32B9,
631 0X32BA,
632 0X32BB,
633 0X32BC,
634 0X32BD,
635 0X32BE,
636 0X32BF,
637 0X3405,
638 0X3483,
639 0X382A,
640 0X3B4D,
641 0X4E00,
642 0X4E03,
643 0X4E07,
644 0X4E09,
645 0X4E5D,
646 0X4E8C,
647 0X4E94,
648 0X4E96,
649 0X4EBF,
650 0X4EC0,
651 0X4EDF,
652 0X4EE8,
653 0X4F0D,
654 0X4F70,
655 0X5104,
656 0X5146,
657 0X5169,
658 0X516B,
659 0X516D,
660 0X5341,
661 0X5343,
662 0X5344,
663 0X5345,
664 0X534C,
665 0X53C1,
666 0X53C2,
667 0X53C3,
668 0X53C4,
669 0X56DB,
670 0X58F1,
671 0X58F9,
672 0X5E7A,
673 0X5EFE,
674 0X5EFF,
675 0X5F0C,
676 0X5F0D,
677 0X5F0E,
678 0X5F10,
679 0X62FE,
680 0X634C,
681 0X67D2,
682 0X6F06,
683 0X7396,
684 0X767E,
685 0X8086,
686 0X842C,
687 0X8CAE,
688 0X8CB3,
689 0X8D30,
690 0X9621,
691 0X9646,
692 0X964C,
693 0X9678,
694 0X96F6,
695 0XA620,
696 0XA621,
697 0XA622,
698 0XA623,
699 0XA624,
700 0XA625,
701 0XA626,
702 0XA627,
703 0XA628,
704 0XA629,
705 0XA6E6,
706 0XA6E7,
707 0XA6E8,
708 0XA6E9,
709 0XA6EA,
710 0XA6EB,
711 0XA6EC,
712 0XA6ED,
713 0XA6EE,
714 0XA6EF,
715 0XA830,
716 0XA831,
717 0XA832,
718 0XA833,
719 0XA834,
720 0XA835,
721 0XA8D0,
722 0XA8D1,
723 0XA8D2,
724 0XA8D3,
725 0XA8D4,
726 0XA8D5,
727 0XA8D6,
728 0XA8D7,
729 0XA8D8,
730 0XA8D9,
731 0XA900,
732 0XA901,
733 0XA902,
734 0XA903,
735 0XA904,
736 0XA905,
737 0XA906,
738 0XA907,
739 0XA908,
740 0XA909,
741 0XA9D0,
742 0XA9D1,
743 0XA9D2,
744 0XA9D3,
745 0XA9D4,
746 0XA9D5,
747 0XA9D6,
748 0XA9D7,
749 0XA9D8,
750 0XA9D9,
751 0XA9F0,
752 0XA9F1,
753 0XA9F2,
754 0XA9F3,
755 0XA9F4,
756 0XA9F5,
757 0XA9F6,
758 0XA9F7,
759 0XA9F8,
760 0XA9F9,
761 0XAA50,
762 0XAA51,
763 0XAA52,
764 0XAA53,
765 0XAA54,
766 0XAA55,
767 0XAA56,
768 0XAA57,
769 0XAA58,
770 0XAA59,
771 0XABF0,
772 0XABF1,
773 0XABF2,
774 0XABF3,
775 0XABF4,
776 0XABF5,
777 0XABF6,
778 0XABF7,
779 0XABF8,
780 0XABF9,
781 0XF96B,
782 0XF973,
783 0XF978,
784 0XF9B2,
785 0XF9D1,
786 0XF9D3,
787 0XF9FD,
788 0XFF10,
789 0XFF11,
790 0XFF12,
791 0XFF13,
792 0XFF14,
793 0XFF15,
794 0XFF16,
795 0XFF17,
796 0XFF18,
797 0XFF19,
798 0X10107,
799 0X10108,
800 0X10109,
801 0X1010A,
802 0X1010B,
803 0X1010C,
804 0X1010D,
805 0X1010E,
806 0X1010F,
807 0X10110,
808 0X10111,
809 0X10112,
810 0X10113,
811 0X10114,
812 0X10115,
813 0X10116,
814 0X10117,
815 0X10118,
816 0X10119,
817 0X1011A,
818 0X1011B,
819 0X1011C,
820 0X1011D,
821 0X1011E,
822 0X1011F,
823 0X10120,
824 0X10121,
825 0X10122,
826 0X10123,
827 0X10124,
828 0X10125,
829 0X10126,
830 0X10127,
831 0X10128,
832 0X10129,
833 0X1012A,
834 0X1012B,
835 0X1012C,
836 0X1012D,
837 0X1012E,
838 0X1012F,
839 0X10130,
840 0X10131,
841 0X10132,
842 0X10133,
843 0X10140,
844 0X10141,
845 0X10142,
846 0X10143,
847 0X10144,
848 0X10145,
849 0X10146,
850 0X10147,
851 0X10148,
852 0X10149,
853 0X1014A,
854 0X1014B,
855 0X1014C,
856 0X1014D,
857 0X1014E,
858 0X1014F,
859 0X10150,
860 0X10151,
861 0X10152,
862 0X10153,
863 0X10154,
864 0X10155,
865 0X10156,
866 0X10157,
867 0X10158,
868 0X10159,
869 0X1015A,
870 0X1015B,
871 0X1015C,
872 0X1015D,
873 0X1015E,
874 0X1015F,
875 0X10160,
876 0X10161,
877 0X10162,
878 0X10163,
879 0X10164,
880 0X10165,
881 0X10166,
882 0X10167,
883 0X10168,
884 0X10169,
885 0X1016A,
886 0X1016B,
887 0X1016C,
888 0X1016D,
889 0X1016E,
890 0X1016F,
891 0X10170,
892 0X10171,
893 0X10172,
894 0X10173,
895 0X10174,
896 0X10175,
897 0X10176,
898 0X10177,
899 0X10178,
900 0X1018A,
901 0X1018B,
902 0X102E1,
903 0X102E2,
904 0X102E3,
905 0X102E4,
906 0X102E5,
907 0X102E6,
908 0X102E7,
909 0X102E8,
910 0X102E9,
911 0X102EA,
912 0X102EB,
913 0X102EC,
914 0X102ED,
915 0X102EE,
916 0X102EF,
917 0X102F0,
918 0X102F1,
919 0X102F2,
920 0X102F3,
921 0X102F4,
922 0X102F5,
923 0X102F6,
924 0X102F7,
925 0X102F8,
926 0X102F9,
927 0X102FA,
928 0X102FB,
929 0X10320,
930 0X10321,
931 0X10322,
932 0X10323,
933 0X10341,
934 0X1034A,
935 0X103D1,
936 0X103D2,
937 0X103D3,
938 0X103D4,
939 0X103D5,
940 0X104A0,
941 0X104A1,
942 0X104A2,
943 0X104A3,
944 0X104A4,
945 0X104A5,
946 0X104A6,
947 0X104A7,
948 0X104A8,
949 0X104A9,
950 0X10858,
951 0X10859,
952 0X1085A,
953 0X1085B,
954 0X1085C,
955 0X1085D,
956 0X1085E,
957 0X1085F,
958 0X10879,
959 0X1087A,
960 0X1087B,
961 0X1087C,
962 0X1087D,
963 0X1087E,
964 0X1087F,
965 0X108A7,
966 0X108A8,
967 0X108A9,
968 0X108AA,
969 0X108AB,
970 0X108AC,
971 0X108AD,
972 0X108AE,
973 0X108AF,
974 0X108FB,
975 0X108FC,
976 0X108FD,
977 0X108FE,
978 0X108FF,
979 0X10916,
980 0X10917,
981 0X10918,
982 0X10919,
983 0X1091A,
984 0X1091B,
985 0X109BC,
986 0X109BD,
987 0X109C0,
988 0X109C1,
989 0X109C2,
990 0X109C3,
991 0X109C4,
992 0X109C5,
993 0X109C6,
994 0X109C7,
995 0X109C8,
996 0X109C9,
997 0X109CA,
998 0X109CB,
999 0X109CC,
1000 0X109CD,
1001 0X109CE,
1002 0X109CF,
1003 0X109D2,
1004 0X109D3,
1005 0X109D4,
1006 0X109D5,
1007 0X109D6,
1008 0X109D7,
1009 0X109D8,
1010 0X109D9,
1011 0X109DA,
1012 0X109DB,
1013 0X109DC,
1014 0X109DD,
1015 0X109DE,
1016 0X109DF,
1017 0X109E0,
1018 0X109E1,
1019 0X109E2,
1020 0X109E3,
1021 0X109E4,
1022 0X109E5,
1023 0X109E6,
1024 0X109E7,
1025 0X109E8,
1026 0X109E9,
1027 0X109EA,
1028 0X109EB,
1029 0X109EC,
1030 0X109ED,
1031 0X109EE,
1032 0X109EF,
1033 0X109F0,
1034 0X109F1,
1035 0X109F2,
1036 0X109F3,
1037 0X109F4,
1038 0X109F5,
1039 0X109F6,
1040 0X109F7,
1041 0X109F8,
1042 0X109F9,
1043 0X109FA,
1044 0X109FB,
1045 0X109FC,
1046 0X109FD,
1047 0X109FE,
1048 0X109FF,
1049 0X10A40,
1050 0X10A41,
1051 0X10A42,
1052 0X10A43,
1053 0X10A44,
1054 0X10A45,
1055 0X10A46,
1056 0X10A47,
1057 0X10A48,
1058 0X10A7D,
1059 0X10A7E,
1060 0X10A9D,
1061 0X10A9E,
1062 0X10A9F,
1063 0X10AEB,
1064 0X10AEC,
1065 0X10AED,
1066 0X10AEE,
1067 0X10AEF,
1068 0X10B58,
1069 0X10B59,
1070 0X10B5A,
1071 0X10B5B,
1072 0X10B5C,
1073 0X10B5D,
1074 0X10B5E,
1075 0X10B5F,
1076 0X10B78,
1077 0X10B79,
1078 0X10B7A,
1079 0X10B7B,
1080 0X10B7C,
1081 0X10B7D,
1082 0X10B7E,
1083 0X10B7F,
1084 0X10BA9,
1085 0X10BAA,
1086 0X10BAB,
1087 0X10BAC,
1088 0X10BAD,
1089 0X10BAE,
1090 0X10BAF,
1091 0X10CFA,
1092 0X10CFB,
1093 0X10CFC,
1094 0X10CFD,
1095 0X10CFE,
1096 0X10CFF,
1097 0X10D30,
1098 0X10D31,
1099 0X10D32,
1100 0X10D33,
1101 0X10D34,
1102 0X10D35,
1103 0X10D36,
1104 0X10D37,
1105 0X10D38,
1106 0X10D39,
1107 0X10E60,
1108 0X10E61,
1109 0X10E62,
1110 0X10E63,
1111 0X10E64,
1112 0X10E65,
1113 0X10E66,
1114 0X10E67,
1115 0X10E68,
1116 0X10E69,
1117 0X10E6A,
1118 0X10E6B,
1119 0X10E6C,
1120 0X10E6D,
1121 0X10E6E,
1122 0X10E6F,
1123 0X10E70,
1124 0X10E71,
1125 0X10E72,
1126 0X10E73,
1127 0X10E74,
1128 0X10E75,
1129 0X10E76,
1130 0X10E77,
1131 0X10E78,
1132 0X10E79,
1133 0X10E7A,
1134 0X10E7B,
1135 0X10E7C,
1136 0X10E7D,
1137 0X10E7E,
1138 0X10F1D,
1139 0X10F1E,
1140 0X10F1F,
1141 0X10F20,
1142 0X10F21,
1143 0X10F22,
1144 0X10F23,
1145 0X10F24,
1146 0X10F25,
1147 0X10F26,
1148 0X10F51,
1149 0X10F52,
1150 0X10F53,
1151 0X10F54,
1152 0X11052,
1153 0X11053,
1154 0X11054,
1155 0X11055,
1156 0X11056,
1157 0X11057,
1158 0X11058,
1159 0X11059,
1160 0X1105A,
1161 0X1105B,
1162 0X1105C,
1163 0X1105D,
1164 0X1105E,
1165 0X1105F,
1166 0X11060,
1167 0X11061,
1168 0X11062,
1169 0X11063,
1170 0X11064,
1171 0X11065,
1172 0X11066,
1173 0X11067,
1174 0X11068,
1175 0X11069,
1176 0X1106A,
1177 0X1106B,
1178 0X1106C,
1179 0X1106D,
1180 0X1106E,
1181 0X1106F,
1182 0X110F0,
1183 0X110F1,
1184 0X110F2,
1185 0X110F3,
1186 0X110F4,
1187 0X110F5,
1188 0X110F6,
1189 0X110F7,
1190 0X110F8,
1191 0X110F9,
1192 0X11136,
1193 0X11137,
1194 0X11138,
1195 0X11139,
1196 0X1113A,
1197 0X1113B,
1198 0X1113C,
1199 0X1113D,
1200 0X1113E,
1201 0X1113F,
1202 0X111D0,
1203 0X111D1,
1204 0X111D2,
1205 0X111D3,
1206 0X111D4,
1207 0X111D5,
1208 0X111D6,
1209 0X111D7,
1210 0X111D8,
1211 0X111D9,
1212 0X111E1,
1213 0X111E2,
1214 0X111E3,
1215 0X111E4,
1216 0X111E5,
1217 0X111E6,
1218 0X111E7,
1219 0X111E8,
1220 0X111E9,
1221 0X111EA,
1222 0X111EB,
1223 0X111EC,
1224 0X111ED,
1225 0X111EE,
1226 0X111EF,
1227 0X111F0,
1228 0X111F1,
1229 0X111F2,
1230 0X111F3,
1231 0X111F4,
1232 0X112F0,
1233 0X112F1,
1234 0X112F2,
1235 0X112F3,
1236 0X112F4,
1237 0X112F5,
1238 0X112F6,
1239 0X112F7,
1240 0X112F8,
1241 0X112F9,
1242 0X11450,
1243 0X11451,
1244 0X11452,
1245 0X11453,
1246 0X11454,
1247 0X11455,
1248 0X11456,
1249 0X11457,
1250 0X11458,
1251 0X11459,
1252 0X114D0,
1253 0X114D1,
1254 0X114D2,
1255 0X114D3,
1256 0X114D4,
1257 0X114D5,
1258 0X114D6,
1259 0X114D7,
1260 0X114D8,
1261 0X114D9,
1262 0X11650,
1263 0X11651,
1264 0X11652,
1265 0X11653,
1266 0X11654,
1267 0X11655,
1268 0X11656,
1269 0X11657,
1270 0X11658,
1271 0X11659,
1272 0X116C0,
1273 0X116C1,
1274 0X116C2,
1275 0X116C3,
1276 0X116C4,
1277 0X116C5,
1278 0X116C6,
1279 0X116C7,
1280 0X116C8,
1281 0X116C9,
1282 0X11730,
1283 0X11731,
1284 0X11732,
1285 0X11733,
1286 0X11734,
1287 0X11735,
1288 0X11736,
1289 0X11737,
1290 0X11738,
1291 0X11739,
1292 0X1173A,
1293 0X1173B,
1294 0X118E0,
1295 0X118E1,
1296 0X118E2,
1297 0X118E3,
1298 0X118E4,
1299 0X118E5,
1300 0X118E6,
1301 0X118E7,
1302 0X118E8,
1303 0X118E9,
1304 0X118EA,
1305 0X118EB,
1306 0X118EC,
1307 0X118ED,
1308 0X118EE,
1309 0X118EF,
1310 0X118F0,
1311 0X118F1,
1312 0X118F2,
1313 0X11C50,
1314 0X11C51,
1315 0X11C52,
1316 0X11C53,
1317 0X11C54,
1318 0X11C55,
1319 0X11C56,
1320 0X11C57,
1321 0X11C58,
1322 0X11C59,
1323 0X11C5A,
1324 0X11C5B,
1325 0X11C5C,
1326 0X11C5D,
1327 0X11C5E,
1328 0X11C5F,
1329 0X11C60,
1330 0X11C61,
1331 0X11C62,
1332 0X11C63,
1333 0X11C64,
1334 0X11C65,
1335 0X11C66,
1336 0X11C67,
1337 0X11C68,
1338 0X11C69,
1339 0X11C6A,
1340 0X11C6B,
1341 0X11C6C,
1342 0X11D50,
1343 0X11D51,
1344 0X11D52,
1345 0X11D53,
1346 0X11D54,
1347 0X11D55,
1348 0X11D56,
1349 0X11D57,
1350 0X11D58,
1351 0X11D59,
1352 0X11DA0,
1353 0X11DA1,
1354 0X11DA2,
1355 0X11DA3,
1356 0X11DA4,
1357 0X11DA5,
1358 0X11DA6,
1359 0X11DA7,
1360 0X11DA8,
1361 0X11DA9,
1362 0X12400,
1363 0X12401,
1364 0X12402,
1365 0X12403,
1366 0X12404,
1367 0X12405,
1368 0X12406,
1369 0X12407,
1370 0X12408,
1371 0X12409,
1372 0X1240A,
1373 0X1240B,
1374 0X1240C,
1375 0X1240D,
1376 0X1240E,
1377 0X1240F,
1378 0X12410,
1379 0X12411,
1380 0X12412,
1381 0X12413,
1382 0X12414,
1383 0X12415,
1384 0X12416,
1385 0X12417,
1386 0X12418,
1387 0X12419,
1388 0X1241A,
1389 0X1241B,
1390 0X1241C,
1391 0X1241D,
1392 0X1241E,
1393 0X1241F,
1394 0X12420,
1395 0X12421,
1396 0X12422,
1397 0X12423,
1398 0X12424,
1399 0X12425,
1400 0X12426,
1401 0X12427,
1402 0X12428,
1403 0X12429,
1404 0X1242A,
1405 0X1242B,
1406 0X1242C,
1407 0X1242D,
1408 0X1242E,
1409 0X1242F,
1410 0X12430,
1411 0X12431,
1412 0X12432,
1413 0X12433,
1414 0X12434,
1415 0X12435,
1416 0X12436,
1417 0X12437,
1418 0X12438,
1419 0X12439,
1420 0X1243A,
1421 0X1243B,
1422 0X1243C,
1423 0X1243D,
1424 0X1243E,
1425 0X1243F,
1426 0X12440,
1427 0X12441,
1428 0X12442,
1429 0X12443,
1430 0X12444,
1431 0X12445,
1432 0X12446,
1433 0X12447,
1434 0X12448,
1435 0X12449,
1436 0X1244A,
1437 0X1244B,
1438 0X1244C,
1439 0X1244D,
1440 0X1244E,
1441 0X1244F,
1442 0X12450,
1443 0X12451,
1444 0X12452,
1445 0X12453,
1446 0X12454,
1447 0X12455,
1448 0X12456,
1449 0X12457,
1450 0X12458,
1451 0X12459,
1452 0X1245A,
1453 0X1245B,
1454 0X1245C,
1455 0X1245D,
1456 0X1245E,
1457 0X1245F,
1458 0X12460,
1459 0X12461,
1460 0X12462,
1461 0X12463,
1462 0X12464,
1463 0X12465,
1464 0X12466,
1465 0X12467,
1466 0X12468,
1467 0X12469,
1468 0X1246A,
1469 0X1246B,
1470 0X1246C,
1471 0X1246D,
1472 0X1246E,
1473 0X16A60,
1474 0X16A61,
1475 0X16A62,
1476 0X16A63,
1477 0X16A64,
1478 0X16A65,
1479 0X16A66,
1480 0X16A67,
1481 0X16A68,
1482 0X16A69,
1483 0X16B50,
1484 0X16B51,
1485 0X16B52,
1486 0X16B53,
1487 0X16B54,
1488 0X16B55,
1489 0X16B56,
1490 0X16B57,
1491 0X16B58,
1492 0X16B59,
1493 0X16B5B,
1494 0X16B5C,
1495 0X16B5D,
1496 0X16B5E,
1497 0X16B5F,
1498 0X16B60,
1499 0X16B61,
1500 0X16E80,
1501 0X16E81,
1502 0X16E82,
1503 0X16E83,
1504 0X16E84,
1505 0X16E85,
1506 0X16E86,
1507 0X16E87,
1508 0X16E88,
1509 0X16E89,
1510 0X16E8A,
1511 0X16E8B,
1512 0X16E8C,
1513 0X16E8D,
1514 0X16E8E,
1515 0X16E8F,
1516 0X16E90,
1517 0X16E91,
1518 0X16E92,
1519 0X16E93,
1520 0X16E94,
1521 0X16E95,
1522 0X16E96,
1523 0X1D2E0,
1524 0X1D2E1,
1525 0X1D2E2,
1526 0X1D2E3,
1527 0X1D2E4,
1528 0X1D2E5,
1529 0X1D2E6,
1530 0X1D2E7,
1531 0X1D2E8,
1532 0X1D2E9,
1533 0X1D2EA,
1534 0X1D2EB,
1535 0X1D2EC,
1536 0X1D2ED,
1537 0X1D2EE,
1538 0X1D2EF,
1539 0X1D2F0,
1540 0X1D2F1,
1541 0X1D2F2,
1542 0X1D2F3,
1543 0X1D360,
1544 0X1D361,
1545 0X1D362,
1546 0X1D363,
1547 0X1D364,
1548 0X1D365,
1549 0X1D366,
1550 0X1D367,
1551 0X1D368,
1552 0X1D369,
1553 0X1D36A,
1554 0X1D36B,
1555 0X1D36C,
1556 0X1D36D,
1557 0X1D36E,
1558 0X1D36F,
1559 0X1D370,
1560 0X1D371,
1561 0X1D372,
1562 0X1D373,
1563 0X1D374,
1564 0X1D375,
1565 0X1D376,
1566 0X1D377,
1567 0X1D378,
1568 0X1D7CE,
1569 0X1D7CF,
1570 0X1D7D0,
1571 0X1D7D1,
1572 0X1D7D2,
1573 0X1D7D3,
1574 0X1D7D4,
1575 0X1D7D5,
1576 0X1D7D6,
1577 0X1D7D7,
1578 0X1D7D8,
1579 0X1D7D9,
1580 0X1D7DA,
1581 0X1D7DB,
1582 0X1D7DC,
1583 0X1D7DD,
1584 0X1D7DE,
1585 0X1D7DF,
1586 0X1D7E0,
1587 0X1D7E1,
1588 0X1D7E2,
1589 0X1D7E3,
1590 0X1D7E4,
1591 0X1D7E5,
1592 0X1D7E6,
1593 0X1D7E7,
1594 0X1D7E8,
1595 0X1D7E9,
1596 0X1D7EA,
1597 0X1D7EB,
1598 0X1D7EC,
1599 0X1D7ED,
1600 0X1D7EE,
1601 0X1D7EF,
1602 0X1D7F0,
1603 0X1D7F1,
1604 0X1D7F2,
1605 0X1D7F3,
1606 0X1D7F4,
1607 0X1D7F5,
1608 0X1D7F6,
1609 0X1D7F7,
1610 0X1D7F8,
1611 0X1D7F9,
1612 0X1D7FA,
1613 0X1D7FB,
1614 0X1D7FC,
1615 0X1D7FD,
1616 0X1D7FE,
1617 0X1D7FF,
1618 0X1E8C7,
1619 0X1E8C8,
1620 0X1E8C9,
1621 0X1E8CA,
1622 0X1E8CB,
1623 0X1E8CC,
1624 0X1E8CD,
1625 0X1E8CE,
1626 0X1E8CF,
1627 0X1E950,
1628 0X1E951,
1629 0X1E952,
1630 0X1E953,
1631 0X1E954,
1632 0X1E955,
1633 0X1E956,
1634 0X1E957,
1635 0X1E958,
1636 0X1E959,
1637 0X1EC71,
1638 0X1EC72,
1639 0X1EC73,
1640 0X1EC74,
1641 0X1EC75,
1642 0X1EC76,
1643 0X1EC77,
1644 0X1EC78,
1645 0X1EC79,
1646 0X1EC7A,
1647 0X1EC7B,
1648 0X1EC7C,
1649 0X1EC7D,
1650 0X1EC7E,
1651 0X1EC7F,
1652 0X1EC80,
1653 0X1EC81,
1654 0X1EC82,
1655 0X1EC83,
1656 0X1EC84,
1657 0X1EC85,
1658 0X1EC86,
1659 0X1EC87,
1660 0X1EC88,
1661 0X1EC89,
1662 0X1EC8A,
1663 0X1EC8B,
1664 0X1EC8C,
1665 0X1EC8D,
1666 0X1EC8E,
1667 0X1EC8F,
1668 0X1EC90,
1669 0X1EC91,
1670 0X1EC92,
1671 0X1EC93,
1672 0X1EC94,
1673 0X1EC95,
1674 0X1EC96,
1675 0X1EC97,
1676 0X1EC98,
1677 0X1EC99,
1678 0X1EC9A,
1679 0X1EC9B,
1680 0X1EC9C,
1681 0X1EC9D,
1682 0X1EC9E,
1683 0X1EC9F,
1684 0X1ECA0,
1685 0X1ECA1,
1686 0X1ECA2,
1687 0X1ECA3,
1688 0X1ECA4,
1689 0X1ECA5,
1690 0X1ECA6,
1691 0X1ECA7,
1692 0X1ECA8,
1693 0X1ECA9,
1694 0X1ECAA,
1695 0X1ECAB,
1696 0X1ECAD,
1697 0X1ECAE,
1698 0X1ECAF,
1699 0X1ECB1,
1700 0X1ECB2,
1701 0X1ECB3,
1702 0X1ECB4,
1703 0X1F100,
1704 0X1F101,
1705 0X1F102,
1706 0X1F103,
1707 0X1F104,
1708 0X1F105,
1709 0X1F106,
1710 0X1F107,
1711 0X1F108,
1712 0X1F109,
1713 0X1F10A,
1714 0X1F10B,
1715 0X1F10C,
1716 0X20001,
1717 0X20064,
1718 0X200E2,
1719 0X20121,
1720 0X2092A,
1721 0X20983,
1722 0X2098C,
1723 0X2099C,
1724 0X20AEA,
1725 0X20AFD,
1726 0X20B19,
1727 0X22390,
1728 0X22998,
1729 0X23B1B,
1730 0X2626D,
1731 0X2F890,
1732 )
1733
1734 # Some code that can be used to create the above list of hex numbers.
1735 if __name__ == "__main__":
1736 import unicodedata
1737 from natsort.compat.py23 import py23_range, py23_unichr
1738
1739 hex_chars = []
1740 for i in py23_range(0X110000):
1741 try:
1742 a = py23_unichr(i)
1743 except ValueError:
1744 break
1745 if a in set("0123456789"):
1746 continue
1747 if unicodedata.numeric(a, None) is not None:
1748 hex_chars.append(i)
1749
1750 print(", ".join(["0X{:X}".format(i) for i in hex_chars]))
00 # -*- coding: utf-8 -*-
11 """
22 Utilities and definitions for natsort, mostly all used to define
3 the _natsort_key function.
3 the natsort_key function.
44
55 SOME CONVENTIONS USED IN THIS FILE.
66
2626
2727 >>> def factory(parameter):
2828 ... val = 'yes' if parameter else 'no'
29 ... def closure(x, val=val):
30 ... return '{} {}'.format(val, x)
29 ... def closure(x, _val=val):
30 ... return '{} {}'.format(_val, x)
3131 ... return closure
3232 ...
3333
3737 and thus has a slightly improved performance at runtime.
3838
3939 """
40 from __future__ import (
41 print_function,
42 division,
43 unicode_literals,
44 absolute_import
45 )
46
47 # Std. lib imports.
40 from __future__ import absolute_import, division, print_function, unicode_literals
41
4842 import re
49 from warnings import warn
50 from os import curdir as os_curdir, pardir as os_pardir
51 from os.path import split as path_split, splitext as path_splitext
52 from itertools import chain as ichain
5343 from collections import deque
5444 from functools import partial, reduce
45 from itertools import chain as ichain
5546 from operator import methodcaller
47 from os import curdir as os_curdir
48 from os import pardir as os_pardir
49 from os.path import split as path_split
50 from os.path import splitext as path_splitext
5651 from unicodedata import normalize
57
58 # Local imports.
59 from natsort.ns_enum import ns
60 from natsort.unicode_numbers import numeric_no_decimals, digits_no_decimals
52 from warnings import warn
53
54 from natsort.compat.fastnumbers import fast_float, fast_int
55 from natsort.compat.locale import get_decimal_point, get_strxfrm, get_thousands_sep
6156 from natsort.compat.pathlib import PurePath, has_pathlib
62 from natsort.compat.locale import (
63 get_strxfrm,
64 get_thousands_sep,
65 get_decimal_point,
57 from natsort.compat.py23 import (
58 NEWPY,
59 PY_VERSION,
60 py23_filter,
61 py23_map,
62 py23_str,
63 u_format,
6664 )
67 from natsort.compat.py23 import (
68 py23_str,
69 py23_map,
70 py23_filter,
71 PY_VERSION,
72 NEWPY,
73 )
74 from natsort.compat.fastnumbers import (
75 fast_float,
76 fast_int,
77 )
65 from natsort.ns_enum import ns, ns_DUMB
66 from natsort.unicode_numbers import digits_no_decimals, numeric_no_decimals
67
7868 if PY_VERSION >= 3:
7969 long = int
8070
81 # The regex that locates floats - include Unicode numerals.
82 _nnd = numeric_no_decimals
83 _exp = r'(?:[eE][-+]?\d+)?'
84 _num = r'(?:\d+\.?\d*|\.\d+)'
85 _float_sign_exp_re = r'([-+]?{0}{1}|[{2}])'
86 _float_sign_exp_re = _float_sign_exp_re.format(_num, _exp, _nnd)
87 _float_sign_exp_re = re.compile(_float_sign_exp_re, flags=re.U)
88 _float_nosign_exp_re = r'({0}{1}|[{2}])'
89 _float_nosign_exp_re = _float_nosign_exp_re.format(_num, _exp, _nnd)
90 _float_nosign_exp_re = re.compile(_float_nosign_exp_re, flags=re.U)
91 _float_sign_noexp_re = r'([-+]?{0}|[{1}])'
92 _float_sign_noexp_re = _float_sign_noexp_re.format(_num, _nnd)
93 _float_sign_noexp_re = re.compile(_float_sign_noexp_re, flags=re.U)
94 _float_nosign_noexp_re = r'({0}|[{1}])'
95 _float_nosign_noexp_re = _float_nosign_noexp_re.format(_num, _nnd)
96 _float_nosign_noexp_re = re.compile(_float_nosign_noexp_re, flags=re.U)
97
98 # Integer regexes - include Unicode digits.
99 _int_nosign_re = r'(\d+|[{0}])'.format(digits_no_decimals)
100 _int_nosign_re = re.compile(_int_nosign_re, flags=re.U)
101 _int_sign_re = r'([-+]?\d+|[{0}])'.format(digits_no_decimals)
102 _int_sign_re = re.compile(_int_sign_re, flags=re.U)
103
104 # This dict will help select the correct regex and number conversion function.
105 _regex_chooser = {
106 (ns.F | ns.S): _float_sign_exp_re,
107 (ns.F | ns.S | ns.N): _float_sign_noexp_re,
108 (ns.F | ns.U): _float_nosign_exp_re,
109 (ns.F | ns.U | ns.N): _float_nosign_noexp_re,
110 (ns.I | ns.S): _int_sign_re,
111 (ns.I | ns.S | ns.N): _int_sign_re,
112 (ns.I | ns.U): _int_nosign_re,
113 (ns.I | ns.U | ns.N): _int_nosign_re,
114 }
71
72 class NumericalRegularExpressions(object):
73 """
74 Container of regular expressions that match numbers.
75
76 The numbers also account for unicode non-decimal characters.
77
78 Not intended to be made an instance - use class methods only.
79 """
80
81 # All unicode numeric characters (minus the decimal characters).
82 numeric = numeric_no_decimals
83 # All unicode digit characters (minus the decimal characters).
84 digits = digits_no_decimals
85 # Regular expression to match exponential component of a float.
86 exp = r"(?:[eE][-+]?\d+)?"
87 # Regular expression to match a floating point number.
88 float_num = r"(?:\d+\.?\d*|\.\d+)"
89
90 @classmethod
91 def _construct_regex(cls, fmt):
92 """Given a format string, construct the regex with class attributes."""
93 return re.compile(fmt.format(**vars(cls)), flags=re.U)
94
95 @classmethod
96 def int_sign(cls):
97 """Regular expression to match a signed int."""
98 return cls._construct_regex(r"([-+]?\d+|[{digits}])")
99
100 @classmethod
101 def int_nosign(cls):
102 """Regular expression to match an unsigned int."""
103 return cls._construct_regex(r"(\d+|[{digits}])")
104
105 @classmethod
106 def float_sign_exp(cls):
107 """Regular expression to match a signed float with exponent."""
108 return cls._construct_regex(r"([-+]?{float_num}{exp}|[{numeric}])")
109
110 @classmethod
111 def float_nosign_exp(cls):
112 """Regular expression to match an unsigned float with exponent."""
113 return cls._construct_regex(r"({float_num}{exp}|[{numeric}])")
114
115 @classmethod
116 def float_sign_noexp(cls):
117 """Regular expression to match a signed float without exponent."""
118 return cls._construct_regex(r"([-+]?{float_num}|[{numeric}])")
119
120 @classmethod
121 def float_nosign_noexp(cls):
122 """Regular expression to match an unsigned float without exponent."""
123 return cls._construct_regex(r"({float_num}|[{numeric}])")
124
125
126 def regex_chooser(alg):
127 """
128 Select an appropriate regex for the type of number of interest.
129
130 Parameters
131 ----------
132 alg : ns enum
133 Used to indicate the regular expression to select.
134
135 Returns
136 -------
137 regex : compiled regex object
138 Regular expression object that matches the desired number type.
139
140 """
141 if alg & ns.FLOAT:
142 alg &= ns.FLOAT | ns.SIGNED | ns.NOEXP
143 else:
144 alg &= ns.INT | ns.SIGNED
145
146 return {
147 ns.INT: NumericalRegularExpressions.int_nosign(),
148 ns.FLOAT: NumericalRegularExpressions.float_nosign_exp(),
149 ns.INT | ns.SIGNED: NumericalRegularExpressions.int_sign(),
150 ns.FLOAT | ns.SIGNED: NumericalRegularExpressions.float_sign_exp(),
151 ns.FLOAT | ns.NOEXP: NumericalRegularExpressions.float_nosign_noexp(),
152 ns.FLOAT | ns.SIGNED | ns.NOEXP: NumericalRegularExpressions.float_sign_noexp(),
153 }[alg]
115154
116155
117156 def _no_op(x):
118 """A function that does nothing."""
157 """A function that does nothing and returns the input as-is."""
119158 return x
120159
121160
122161 def _normalize_input_factory(alg):
123 """Create a function that will normalize unicode input data."""
124 normalization_form = 'NFKD' if alg & ns.COMPATIBILITYNORMALIZE else 'NFD'
125
162 """
163 Create a function that will normalize unicode input data.
164
165 Parameters
166 ----------
167 alg : ns enum
168 Used to indicate how to normalize unicode.
169
170 Returns
171 -------
172 func : callable
173 A function that accepts string (unicode) input and returns the
174 the input normalized with the desired normalization scheme.
175
176 """
177 normalization_form = "NFKD" if alg & ns.COMPATIBILITYNORMALIZE else "NFD"
178 wrapped = partial(normalize, normalization_form)
126179 if NEWPY:
127 return partial(normalize, normalization_form)
180 return wrapped
128181 else:
129 def func(x):
130 """Normalize unicode input."""
131 if isinstance(x, py23_str): # unicode
132 return normalize(normalization_form, x)
133 else:
134 return x
135 return func
136
137
138 def _natsort_key(val, key, string_func, bytes_func, num_func):
139 """\
182 return lambda x, _f=wrapped: _f(x) if isinstance(x, py23_str) else x
183
184
185 def natsort_key(val, key, string_func, bytes_func, num_func):
186 """
140187 Key to sort strings and numbers naturally.
141188
142 It works by separating out the numbers from the strings. This function for
143 internal use only. See the natsort_keygen documentation for details of each
144 parameter.
145
146 Parameters
147 ----------
148 val : str | unicode
189 It works by splitting the string into components of strings and numbers,
190 and then converting the numbers into actual ints or floats.
191
192 Parameters
193 ----------
194 val : str | unicode | bytes | int | float | iterable
149195 key : callable | None
196 A key to apply to the *val* before any other operations are performed.
150197 string_func : callable
198 If *val* (or the output of *key* if given) is of type *str*, this
199 function will be applied to it. The function must return
200 a tuple.
151201 bytes_func : callable
202 If *val* (or the output of *key* if given) is of type *bytes*, this
203 function will be applied to it. The function must return
204 a tuple.
152205 num_func : callable
206 If *val* (or the output of *key* if given) is not of type *bytes*,
207 *str*, nor is iterable, this function will be applied to it.
208 The function must return a tuple.
153209
154210 Returns
155211 -------
156212 out : tuple
157 The modified value with numbers extracted.
213 The string split into its string and numeric components.
214 It *always* starts with a string, and then alternates
215 between numbers and strings (unless it was applied
216 recursively, in which case it will return tuples of tuples,
217 but the lowest-level tuples will then *always* start with
218 a string etc.).
219
220 See Also
221 --------
222 parse_string_factory
223 parse_bytes_factory
224 parse_number_factory
158225
159226 """
160227
171238 if type(val) in (bytes,):
172239 return bytes_func(val)
173240
174 # Otherwise, assume it is an iterable that must be parses recursively.
241 # Otherwise, assume it is an iterable that must be parsed recursively.
175242 # Do not apply the key recursively.
176243 try:
177 return tuple(_natsort_key(
178 x, None, string_func, bytes_func, num_func
179 ) for x in val)
244 return tuple(
245 natsort_key(x, None, string_func, bytes_func, num_func) for x in val
246 )
180247
181248 # If that failed, it must be a number.
182249 except TypeError:
183250 return num_func(val)
184251
185252
186 def _parse_bytes_factory(alg):
187 """Create a function that will format a bytes string in a tuple."""
253 def parse_bytes_factory(alg):
254 """
255 Create a function that will format a *bytes* object into a tuple.
256
257 Parameters
258 ----------
259 alg : ns enum
260 Indicate how to format the *bytes*.
261
262 Returns
263 -------
264 func : callable
265 A function that accepts *bytes* input and returns a tuple
266 with the formatted *bytes*. Intended to be used as the
267 *bytes_func* argument to *natsort_key*.
268
269 See Also
270 --------
271 natsort_key
272
273 """
188274 # We don't worry about ns.UNGROUPLETTERS | ns.LOCALEALPHA because
189275 # bytes cannot be compared to strings.
190276 if alg & ns.PATH and alg & ns.IGNORECASE:
197283 return lambda x: (x,)
198284
199285
200 def _parse_number_factory(alg, sep, pre_sep):
201 """Create a function that will properly format a number in a tuple."""
202 nan_replace = float('+inf') if alg & ns.NANLAST else float('-inf')
203
204 def func(val, nan_replace=nan_replace, sep=sep):
286 def parse_number_factory(alg, sep, pre_sep):
287 """
288 Create a function that will format a number into a tuple.
289
290 Parameters
291 ----------
292 alg : ns enum
293 Indicate how to format the *bytes*.
294 sep : str
295 The string character to be inserted before the number
296 in the returned tuple.
297 pre_sep : str
298 In the event that *alg* contains ``UNGROUPLETTERS``, this
299 string will be placed in a single-element tuple at the front
300 of the returned nested tuple.
301
302 Returns
303 -------
304 func : callable
305 A function that accepts numeric input (e.g. *int* or *float*)
306 and returns a tuple containing the number with the leading string
307 *sep*. Intended to be used as the *num_func* argument to
308 *natsort_key*.
309
310 See Also
311 --------
312 natsort_key
313
314 """
315 nan_replace = float("+inf") if alg & ns.NANLAST else float("-inf")
316
317 def func(val, _nan_replace=nan_replace, _sep=sep):
205318 """Given a number, place it in a tuple with a leading null string."""
206 return sep, nan_replace if val != val else val
319 return _sep, _nan_replace if val != val else val
207320
208321 # Return the function, possibly wrapping in tuple if PATH is selected.
209322 if alg & ns.PATH and alg & ns.UNGROUPLETTERS and alg & ns.LOCALEALPHA:
216329 return func
217330
218331
219 def _parse_string_factory(alg, sep, splitter,
220 input_transform,
221 component_transform,
222 final_transform):
223 """Create a function that will properly split and format a string."""
332 def parse_string_factory(
333 alg, sep, splitter, input_transform, component_transform, final_transform
334 ):
335 """
336 Create a function that will split and format a *str* into a tuple.
337
338 Parameters
339 ----------
340 alg : ns enum
341 Indicate how to format and split the *str*.
342 sep : str
343 The string character to be inserted between adjacent numeric
344 objects in the returned tuple.
345 splitter : callable
346 A function the will accept a string and returns an iterable
347 of strings where the numbers are separated from the non-numbers.
348 input_transform : callable
349 A function to apply to the string input *before* applying
350 the *splitter* function. Must return a string.
351 component_transform : callable
352 A function that is operated elementwise on the output of
353 *splitter*. It must accept a single string and return either
354 a string or a number.
355 final_transform : callable
356 A function to operate on the return value as a whole. It
357 must accept a tuple and a string argument - the tuple
358 should be the result of applying the above functions, and the
359 string is the original input value. It must return a tuple.
360
361 Returns
362 -------
363 func : callable
364 A function that accepts string input and returns a tuple
365 containing the string split into numeric and non-numeric
366 components, where the numeric components are converted into
367 numeric objects. The first element is *always* a string,
368 and then alternates number then string. Intended to be
369 used as the *string_func* argument to *natsort_key*.
370
371 See Also
372 --------
373 natsort_key
374 input_string_transform_factory
375 string_component_transform_factory
376 final_data_transform_factory
377
378 """
224379 # Sometimes we store the "original" input before transformation,
225380 # sometimes after.
226 orig_after_xfrm = not (alg & ns._DUMB and alg & ns.LOCALEALPHA)
381 orig_after_xfrm = not (alg & ns_DUMB and alg & ns.LOCALEALPHA)
227382 original_func = input_transform if orig_after_xfrm else _no_op
228383 normalize_input = _normalize_input_factory(alg)
229384
233388 # to also be the transformation function.
234389 x = normalize_input(x)
235390 x, original = input_transform(x), original_func(x)
236 x = splitter(x) # Split string into components.
237 x = py23_filter(None, x) # Remove empty strings.
391 x = splitter(x) # Split string into components.
392 x = py23_filter(None, x) # Remove empty strings.
238393 x = py23_map(component_transform, x) # Apply transform on components.
239 x = _sep_inserter(x, sep) # Insert '' between numbers.
240 return final_transform(x, original) # Apply the final transform.
394 x = sep_inserter(x, sep) # Insert '' between numbers.
395 return final_transform(x, original) # Apply the final transform.
241396
242397 return func
243398
244399
245 def _parse_path_factory(str_split):
246 """Create a function that will properly split and format a path."""
247 return lambda x: tuple(py23_map(str_split, _path_splitter(x)))
248
249
250 def _sep_inserter(iterable, sep):
251 """Insert '' between numbers."""
252
253 # Get the first element. If StopIteration is raised, that's OK.
254 # Since we are controlling the types of the input, 'type' is used
255 # instead of 'isinstance' for the small speed advantage it offers.
400 def parse_path_factory(str_split):
401 """
402 Create a function that will properly split and format a path.
403
404 Parameters
405 ----------
406 str_split : callable
407 The output of the *parse_string_factory* function.
408
409 Returns
410 -------
411 func : callable
412 A function that accepts a string or path-like object
413 and splits it into its path components, then passes
414 each component to *str_split* and returns the result
415 as a nested tuple. Can be used as the *string_func*
416 argument to *natsort_key*.
417
418 See Also
419 --------
420 natsort_key
421 parse_string_factory
422
423 """
424 return lambda x: tuple(py23_map(str_split, path_splitter(x)))
425
426
427 def sep_inserter(iterable, sep):
428 """
429 Insert '' between numbers in an iterable.
430
431 Parameters
432 ----------
433 iterable
434 sep : str
435 The string character to be inserted between adjacent numeric objects.
436
437 Yields
438 ------
439 The values of *iterable* in order, with *sep* inserted where adjacent
440 elements are numeric. If the first element in the input is numeric
441 then *sep* will be the first value yielded.
442
443 """
256444 try:
445 # Get the first element. A StopIteration indicates an empty iterable.
446 # Since we are controlling the types of the input, 'type' is used
447 # instead of 'isinstance' for the small speed advantage it offers.
257448 types = (int, float, long)
258449 first = next(iterable)
259450 if type(first) in types:
278469 return
279470
280471
281 def _input_string_transform_factory(alg):
282 """
283 Given a set of natsort algorithms, return the function to operate
284 on the pre-split input string according to the user's request.
472 def input_string_transform_factory(alg):
473 """
474 Create a function to transform a string.
475
476 Parameters
477 ----------
478 alg : ns enum
479 Indicate how to format the *str*.
480
481 Returns
482 -------
483 func : callable
484 A function to be used as the *input_transform* argument to
485 *parse_string_factory*.
486
487 See Also
488 --------
489 parse_string_factory
490
285491 """
286492 # Shortcuts.
287493 lowfirst = alg & ns.LOWERCASEFIRST
288 dumb = alg & ns._DUMB
494 dumb = alg & ns_DUMB
289495
290496 # Build the chain of functions to execute in order.
291497 function_chain = []
292498 if (dumb and not lowfirst) or (lowfirst and not dumb):
293 function_chain.append(methodcaller('swapcase'))
499 function_chain.append(methodcaller("swapcase"))
294500
295501 if alg & ns.IGNORECASE:
296502 if NEWPY:
297 function_chain.append(methodcaller('casefold'))
503 function_chain.append(methodcaller("casefold"))
298504 else:
299 function_chain.append(methodcaller('lower'))
505 function_chain.append(methodcaller("lower"))
300506
301507 if alg & ns.LOCALENUM:
302508 # Create a regular expression that will remove thousands separators.
303 strip_thousands = r'''
509 strip_thousands = r"""
304510 (?<=[0-9]{{1}}) # At least 1 number
305511 (?<![0-9]{{4}}) # No more than 3 numbers
306512 {nodecimal} # Cannot follow decimal
308514 (?=[0-9]{{3}} # Three numbers must follow
309515 ([^0-9]|$) # But a non-number after that
310516 )
311 '''
312 nodecimal = r''
517 """
518 nodecimal = r""
313519 if alg & ns.FLOAT:
314520 # Make a regular expression component that will ensure no
315521 # separators are removed after a decimal point.
316522 d = get_decimal_point()
317 d = r'\.' if d == r'.' else d
318 nodecimal += r'(?<!' + d + r'[0-9])'
319 nodecimal += r'(?<!' + d + r'[0-9]{2})'
320 nodecimal += r'(?<!' + d + r'[0-9]{3})'
321 strip_thousands = strip_thousands.format(thou=get_thousands_sep(),
322 nodecimal=nodecimal)
523 d = r"\." if d == r"." else d
524 nodecimal += r"(?<!" + d + r"[0-9])"
525 nodecimal += r"(?<!" + d + r"[0-9]{2})"
526 nodecimal += r"(?<!" + d + r"[0-9]{3})"
527 strip_thousands = strip_thousands.format(
528 thou=get_thousands_sep(), nodecimal=nodecimal
529 )
323530 strip_thousands = re.compile(strip_thousands, flags=re.VERBOSE)
324 function_chain.append(partial(strip_thousands.sub, ''))
531 function_chain.append(partial(strip_thousands.sub, ""))
325532
326533 # Create a regular expression that will change the decimal point to
327534 # a period if not already a period.
328535 decimal = get_decimal_point()
329 if alg & ns.FLOAT and decimal != '.':
330 switch_decimal = r'(?<=[0-9]){decimal}|{decimal}(?=[0-9])'
536 if alg & ns.FLOAT and decimal != ".":
537 switch_decimal = r"(?<=[0-9]){decimal}|{decimal}(?=[0-9])"
331538 switch_decimal = switch_decimal.format(decimal=decimal)
332539 switch_decimal = re.compile(switch_decimal)
333 function_chain.append(partial(switch_decimal.sub, '.'))
540 function_chain.append(partial(switch_decimal.sub, "."))
334541
335542 # Return the chained functions.
336543 return chain_functions(function_chain)
337544
338545
339 def _string_component_transform_factory(alg):
340 """
341 Given a set of natsort algorithms, return the function to operate
342 on the post-split strings according to the user's request.
546 def string_component_transform_factory(alg):
547 """
548 Create a function to either transform a string or convert to a number.
549
550 Parameters
551 ----------
552 alg : ns enum
553 Indicate how to format the *str*.
554
555 Returns
556 -------
557 func : callable
558 A function to be used as the *component_transform* argument to
559 *parse_string_factory*.
560
561 See Also
562 --------
563 parse_string_factory
564
343565 """
344566 # Shortcuts.
345567 use_locale = alg & ns.LOCALEALPHA
346 dumb = alg & ns._DUMB
568 dumb = alg & ns_DUMB
347569 group_letters = (alg & ns.GROUPLETTERS) or (use_locale and dumb)
348 nan_val = float('+inf') if alg & ns.NANLAST else float('-inf')
570 nan_val = float("+inf") if alg & ns.NANLAST else float("-inf")
349571
350572 # Build the chain of functions to execute in order.
351573 func_chain = []
352574 if group_letters:
353 func_chain.append(_groupletters)
575 func_chain.append(groupletters)
354576 if use_locale:
355577 func_chain.append(get_strxfrm())
356 kwargs = {'key': chain_functions(func_chain)} if func_chain else {}
578 kwargs = {"key": chain_functions(func_chain)} if func_chain else {}
357579
358580 # Return the correct chained functions.
359581 if alg & ns.FLOAT:
360 kwargs['nan'] = nan_val
582 # noinspection PyTypeChecker
583 kwargs["nan"] = nan_val
361584 return partial(fast_float, **kwargs)
362585 else:
363586 return partial(fast_int, **kwargs)
364587
365588
366 def _final_data_transform_factory(alg, sep, pre_sep):
367 """
368 Given a set of natsort algorithms, return the function to operate
369 on the post-parsed strings according to the user's request.
589 def final_data_transform_factory(alg, sep, pre_sep):
590 """
591 Create a function to transform a tuple.
592
593 Parameters
594 ----------
595 alg : ns enum
596 Indicate how to format the *str*.
597 sep : str
598 Separator that was passed to *parse_string_factory*.
599 pre_sep : str
600 String separator to insert at the at the front
601 of the return tuple in the case that the first element
602 is *sep*.
603
604 Returns
605 -------
606 func : callable
607 A function to be used as the *final_transform* argument to
608 *parse_string_factory*.
609
610 See Also
611 --------
612 parse_string_factory
613
370614 """
371615 if alg & ns.UNGROUPLETTERS and alg & ns.LOCALEALPHA:
372 swap = alg & ns._DUMB and alg & ns.LOWERCASEFIRST
373 transform = methodcaller('swapcase') if swap else _no_op
374
375 def func(split_val, val, transform=transform):
616 swap = alg & ns_DUMB and alg & ns.LOWERCASEFIRST
617 transform = methodcaller("swapcase") if swap else _no_op
618
619 def func(split_val, val, _transform=transform, _sep=sep, _pre_sep=pre_sep):
376620 """
377621 Return a tuple with the first character of the first element
378622 of the return value as the first element, and the return value
382626 split_val = tuple(split_val)
383627 if not split_val:
384628 return (), ()
385 elif split_val[0] == sep:
386 return (pre_sep,), split_val
629 elif split_val[0] == _sep:
630 return (_pre_sep,), split_val
387631 else:
388 return (transform(val[0]),), split_val
632 return (_transform(val[0]),), split_val
633
389634 return func
390635 else:
391636 return lambda split_val, val: tuple(split_val)
392637
393638
394 def _groupletters(x, _low=methodcaller('casefold' if NEWPY else 'lower')):
395 """Double all characters, making doubled letters lowercase."""
396 return ''.join(ichain.from_iterable((_low(y), y) for y in x))
639 lower_function = methodcaller("casefold" if NEWPY else "lower")
640
641
642 # noinspection PyIncorrectDocstring
643 @u_format
644 def groupletters(x, _low=lower_function):
645 """
646 Double all characters, making doubled letters lowercase.
647
648 Parameters
649 ----------
650 x : str
651
652 Returns
653 -------
654 str
655
656 Examples
657 --------
658
659 >>> groupletters("Apple")
660 {u}'aAppppllee'
661
662 """
663 return "".join(ichain.from_iterable((_low(y), y) for y in x))
397664
398665
399666 def chain_functions(functions):
410677
411678 Returns
412679 -------
413 A single argument function.
680 func : callable
681 A single argument function.
414682
415683 Examples
416684 --------
432700 return partial(reduce, lambda res, f: f(res), functions)
433701
434702
435 def _do_decoding(s, encoding):
436 """A function to decode a bytes string, or return the object as-is."""
703 def do_decoding(s, encoding):
704 """
705 Helper to decode a *bytes* object, or return the object as-is.
706
707 Parameters
708 ----------
709 s : bytes | object
710 encoding : str
711 The encoding to use to decode *s*.
712
713 Returns
714 -------
715 decoded
716 *str* if *s* was *bytes* and the decoding was successful.
717 *s* if *s* was not *bytes*.
718
719 """
437720 try:
438721 return s.decode(encoding)
439 except UnicodeError:
440 raise
441722 except (AttributeError, TypeError):
442723 return s
443724
444725
445 def _path_splitter(s, _d_match=re.compile(r'\.\d').match):
446 """Split a string into its path components. Assumes a string is a path."""
447 # If a PathLib Object, use it's functionality to perform the split.
726 # noinspection PyIncorrectDocstring
727 @u_format
728 def path_splitter(s, _d_match=re.compile(r"\.\d").match):
729 """
730 Split a string into its path components.
731
732 Assumes a string is a path or is path-like.
733
734 Parameters
735 ----------
736 s : str | pathlib.Path
737
738 Returns
739 -------
740 split : tuple
741 The path split by directory components and extensions.
742
743 Examples
744 --------
745
746 >>> tuple(path_splitter("/this/thing.ext"))
747 ({u}'/', {u}'this', {u}'thing', {u}'.ext')
748
749 """
448750 if has_pathlib and isinstance(s, PurePath):
449751 s = py23_str(s)
450752 path_parts = deque()
486788 return ichain(path_parts, base_parts)
487789
488790
489 def _args_to_enum(**kwargs):
490 """A function to convert input booleans to an enum-type argument."""
791 def args_to_enum(**kwargs):
792 """
793 A function to convert input booleans to an enum-type argument.
794
795 For internal use only - will be deprecated in a future release.
796 """
491797 alg = 0
492 keys = ('number_type', 'signed', 'exp', 'as_path', 'py3_safe')
798 keys = ("number_type", "signed", "exp", "as_path", "py3_safe")
493799 if any(x not in keys for x in kwargs):
494800 x = set(kwargs) - set(keys)
495 raise TypeError('Invalid argument(s): ' + ', '.join(x))
496 if 'number_type' in kwargs and kwargs['number_type'] is not int:
801 raise TypeError("Invalid argument(s): " + ", ".join(x))
802 if "number_type" in kwargs and kwargs["number_type"] is not int:
497803 msg = "The 'number_type' argument is deprecated as of 3.5.0, "
498804 msg += "please use 'alg=ns.FLOAT', 'alg=ns.INT', or 'alg=ns.VERSION'"
499805 warn(msg, DeprecationWarning)
500 alg |= (ns.FLOAT * bool(kwargs['number_type'] is float))
501 alg |= (ns.INT * bool(kwargs['number_type'] in (int, None)))
502 alg |= (ns.SIGNED * (kwargs['number_type'] not in (float, None)))
503 if 'signed' in kwargs and kwargs['signed'] is not None:
806 alg |= ns.FLOAT * bool(kwargs["number_type"] is float)
807 alg |= ns.INT * bool(kwargs["number_type"] in (int, None))
808 alg |= ns.SIGNED * (kwargs["number_type"] not in (float, None))
809 if "signed" in kwargs and kwargs["signed"] is not None:
504810 msg = "The 'signed' argument is deprecated as of 3.5.0, "
505811 msg += "please use 'alg=ns.SIGNED'."
506812 warn(msg, DeprecationWarning)
507 alg |= (ns.SIGNED * bool(kwargs['signed']))
508 if 'exp' in kwargs and kwargs['exp'] is not None:
813 alg |= ns.SIGNED * bool(kwargs["signed"])
814 if "exp" in kwargs and kwargs["exp"] is not None:
509815 msg = "The 'exp' argument is deprecated as of 3.5.0, "
510816 msg += "please use 'alg=ns.NOEXP'."
511817 warn(msg, DeprecationWarning)
512 alg |= (ns.NOEXP * (not kwargs['exp']))
513 if 'as_path' in kwargs and kwargs['as_path'] is not None:
818 alg |= ns.NOEXP * (not kwargs["exp"])
819 if "as_path" in kwargs and kwargs["as_path"] is not None:
514820 msg = "The 'as_path' argument is deprecated as of 3.5.0, "
515821 msg += "please use 'alg=ns.PATH'."
516822 warn(msg, DeprecationWarning)
517 alg |= (ns.PATH * kwargs['as_path'])
823 alg |= ns.PATH * kwargs["as_path"]
518824 return alg
00 [bumpversion]
1 current_version = 5.3.3
1 current_version = 5.4.1
22 commit = True
33 tag = True
44 tag_name = {new_version}
2424 Programming Language :: Python :: 2.6
2525 Programming Language :: Python :: 2.7
2626 Programming Language :: Python :: 3
27 Programming Language :: Python :: 3.3
2827 Programming Language :: Python :: 3.4
2928 Programming Language :: Python :: 3.5
3029 Programming Language :: Python :: 3.6
30 Programming Language :: Python :: 3.7
3131 Topic :: Scientific/Engineering :: Information Analysis
3232 Topic :: Utilities
3333 Topic :: Text Processing
4040
4141 [bumpversion:file:setup.py]
4242
43 [bumpversion:file:natsort/_version.py]
43 [bumpversion:file:natsort/__init__.py]
4444
4545 [bumpversion:file:docs/source/conf.py]
4646
4747 [bumpversion:file:docs/source/changelog.rst]
48 search = X.X.X
49 replace = {new_version}
50
51 [tool:pytest]
52 flakes-ignore =
53 natsort/compat/py23.py UndefinedName
54 natsort/__init__.py UnusedImport
55 natsort/compat/* UnusedImport
56 docs/source/conf.py ALL
57 test_natsort/test_natsort.py UnusedImport RedefinedWhileUnused
58 test_natsort/test_locale_help.py UnusedImport RedefinedWhileUnused
59 test_natsort/compat/* UnusedImport
60 pep8ignore =
61 natsort/ns_enum.py E126 E241 E123 E221
62 test_natsort/test_*.py E501 E241 E221
63 test_natsort/test_natsort_keygen.py E501 E241 E221 E701
64 test_natsort/profile_natsorted.py ALL
65 docs/source/conf.py ALL
48 search = XX-XX-XXXX v. X.X.X
49 replace = {now:%%m-%%d-%%Y} v. {new_version}
6650
6751 [flake8]
68 max-line-length = 160
69 ignore = E231,E302
52 max-line-length = 89
53 import-order-style = pycharm
54 doctests = True
55 max-complexity = 10
56 exclude =
57 natsort.egg-info,
58 .tox,
59 .cache,
60 .git,
61 __pycache__,
62 build,
63 dist,
64 docs,
65 .venv,
66 natsort/compat/py23.py
7067
00 #! /usr/bin/env python
11
2 from setuptools import setup, find_packages
2 from setuptools import find_packages, setup
33 setup(
44 name='natsort',
5 version='5.3.3',
5 version='5.4.1',
66 packages=find_packages(),
77 install_requires=["argparse; python_version < '2.7'"],
88 entry_points={'console_scripts': ['natsort = natsort.__main__:main']},
+0
-0
test_natsort/compat/__init__.py less more
(Empty file)
+0
-45
test_natsort/compat/locale.py less more
0 # -*- coding: utf-8 -*-
1 from __future__ import (
2 print_function,
3 division,
4 unicode_literals,
5 absolute_import
6 )
7
8 # Std. lib imports.
9 import locale
10
11 # Local imports
12 from natsort.compat.py23 import py23_str, py23_unichr, py23_range
13
14
15 def load_locale(x):
16 """ Convenience to load a locale, trying ISO8859-1 first."""
17 try:
18 locale.setlocale(locale.LC_ALL, str('{0}.ISO8859-1'.format(x)))
19 except locale.Error:
20 locale.setlocale(locale.LC_ALL, str('{0}.UTF-8'.format(x)))
21
22 # Check if de_DE is installed.
23 try:
24 load_locale('de_DE')
25 has_locale_de_DE = True
26 except locale.Error:
27 has_locale_de_DE = False
28
29 # Depending on the python version, use lower or casefold
30 # to make a string lowercase.
31 try:
32 low = py23_str.casefold
33 except AttributeError:
34 low = py23_str.lower
35
36 # There are some unicode values that are known failures on BSD systems
37 # that has nothing to do with natsort (a ValueError is raised by strxfrm).
38 # Let's filter them out.
39 try:
40 bad_uni_chars = set(py23_unichr(x) for x in py23_range(0X10fefd,
41 0X10ffff+1))
42 except ValueError:
43 # Narrow unicode build... no worries.
44 bad_uni_chars = set()
+0
-12
test_natsort/compat/mock.py less more
0 # -*- coding: utf-8 -*-
1 from __future__ import (
2 print_function,
3 division,
4 unicode_literals,
5 absolute_import
6 )
7 # Load mock functions from the right place.
8 try:
9 from unittest.mock import MagicMock, patch, call
10 except ImportError:
11 from mock import MagicMock, patch, call
0 """
1 Fixtures for pytest.
2 """
3
4 import locale
5
6 import pytest
7
8
9 def load_locale(x):
10 """Convenience to load a locale, trying ISO8859-1 first."""
11 try:
12 locale.setlocale(locale.LC_ALL, str("{0}.ISO8859-1".format(x)))
13 except locale.Error:
14 locale.setlocale(locale.LC_ALL, str("{0}.UTF-8".format(x)))
15
16
17 @pytest.fixture()
18 def with_locale_en_us():
19 """Convenience to load the en_US locale - reset when complete."""
20 orig = locale.getlocale()
21 yield load_locale("en_US")
22 locale.setlocale(locale.LC_ALL, orig)
23
24
25 @pytest.fixture()
26 def with_locale_de_de():
27 """
28 Convenience to load the de_DE locale - reset when complete - skip if missing.
29 """
30 orig = locale.getlocale()
31 try:
32 load_locale("de_DE")
33 except locale.Error:
34 pytest.skip("requires de_DE locale to be installed")
35 else:
36 yield
37 finally:
38 locale.setlocale(locale.LC_ALL, orig)
33 inputs and different settings.
44 """
55 from __future__ import print_function
6
67 import cProfile
8 import locale
79 import sys
810
9 sys.path.insert(0, '.')
10 from natsort import natsort_keygen, ns
11 from natsort.compat.py23 import py23_range
12 import locale
13 locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
11 try:
12 from natsort import ns, natsort_keygen
13 from natsort.compat.py23 import py23_range
14 except ImportError:
15 sys.path.insert(0, ".")
16 from natsort import ns, natsort_keygen
17 from natsort.compat.py23 import py23_range
18
19 locale.setlocale(locale.LC_ALL, "en_US.UTF-8")
1420
1521 # Samples to parse
1622 number = 14695498
17 int_string = '43493'
18 float_string = '-434.93e7'
19 plain_string = 'hello world'
20 fancy_string = '7abba9342fdab'
21 a_path = '/p/Folder (1)/file (1).tar.gz'
22 some_bytes = b'these are bytes'
23 a_list = ['hello', 'goodbye', '74']
23 int_string = "43493"
24 float_string = "-434.93e7"
25 plain_string = "hello world"
26 fancy_string = "7abba9342fdab"
27 a_path = "/p/Folder (1)/file (1).tar.gz"
28 some_bytes = b"these are bytes"
29 a_list = ["hello", "goodbye", "74"]
2430
2531 basic_key = natsort_keygen()
2632 real_key = natsort_keygen(alg=ns.REAL)
2935
3036
3137 def prof_time_to_generate():
32 print('*** Generate Plain Key ***')
38 print("*** Generate Plain Key ***")
3339 for _ in py23_range(100000):
3440 natsort_keygen()
35 cProfile.run('prof_time_to_generate()', sort='time')
41
42
43 cProfile.run("prof_time_to_generate()", sort="time")
3644
3745
3846 def prof_parsing(a, msg, key=basic_key):
3947 print(msg)
4048 for _ in py23_range(100000):
4149 key(a)
42 cProfile.run('prof_parsing(int_string, "*** Basic Call, Int as String ***")', sort='time')
43 cProfile.run('prof_parsing(float_string, "*** Basic Call, Float as String ***")', sort='time')
44 cProfile.run('prof_parsing(float_string, "*** Real Call ***", real_key)', sort='time')
45 cProfile.run('prof_parsing(number, "*** Basic Call, Number ***")', sort='time')
46 cProfile.run('prof_parsing(fancy_string, "*** Basic Call, Mixed String ***")', sort='time')
47 cProfile.run('prof_parsing(some_bytes, "*** Basic Call, Byte String ***")', sort='time')
48 cProfile.run('prof_parsing(a_path, "*** Path Call ***", path_key)', sort='time')
49 cProfile.run('prof_parsing(a_list, "*** Basic Call, Recursive ***")', sort='time')
50 cProfile.run('prof_parsing("434,930,000 dollars", "*** Locale Call ***", locale_key)', sort='time')
50
51
52 cProfile.run(
53 'prof_parsing(int_string, "*** Basic Call, Int as String ***")', sort="time"
54 )
55 cProfile.run(
56 'prof_parsing(float_string, "*** Basic Call, Float as String ***")', sort="time"
57 )
58 cProfile.run('prof_parsing(float_string, "*** Real Call ***", real_key)', sort="time")
59 cProfile.run('prof_parsing(number, "*** Basic Call, Number ***")', sort="time")
60 cProfile.run(
61 'prof_parsing(fancy_string, "*** Basic Call, Mixed String ***")', sort="time"
62 )
63 cProfile.run('prof_parsing(some_bytes, "*** Basic Call, Byte String ***")', sort="time")
64 cProfile.run('prof_parsing(a_path, "*** Path Call ***", path_key)', sort="time")
65 cProfile.run('prof_parsing(a_list, "*** Basic Call, Recursive ***")', sort="time")
66 cProfile.run(
67 'prof_parsing("434,930,000 dollars", "*** Locale Call ***", locale_key)',
68 sort="time",
69 )
+0
-417
test_natsort/slow_splitters.py less more
0 # -*- coding: utf-8 -*-
1 """Alternate versions of the splitting functions for testing."""
2 from __future__ import unicode_literals
3
4 import unicodedata
5 import collections
6 import itertools
7 import functools
8 from natsort.unicode_numbers import decimals
9 from natsort.compat.py23 import PY_VERSION, py23_zip
10
11 if PY_VERSION >= 3.0:
12 long = int
13
14 triple_none = None, None, None
15 _sentinel = object()
16 SplitElement = collections.namedtuple('SplitElement',
17 ['isnum', 'val', 'isuni'])
18
19
20 def int_splitter(iterable, signed, sep):
21 """Alternate (slow) method to split a string into numbers."""
22 iterable = unicodedata.normalize('NFD', iterable)
23 split_by_decimal = itertools.groupby(iterable, lambda a: a.isdigit())
24 split_by_decimal = refine_split_grouping(split_by_decimal)
25 split = int_splitter_iter(split_by_decimal, signed)
26 split = sep_inserter(split, sep)
27 return tuple(add_leading_space_if_first_is_num(split, sep))
28
29
30 def float_splitter(iterable, signed, exp, sep):
31 """Alternate (slow) method to split a string into numbers."""
32
33 def number_tester(x):
34 return x.isdecimal() or unicodedata.numeric(x, None) is not None
35
36 iterable = unicodedata.normalize('NFD', iterable)
37 split_by_decimal = itertools.groupby(iterable, number_tester)
38 split_by_decimal = peekable(refine_split_grouping(split_by_decimal))
39 split = float_splitter_iter(split_by_decimal, signed, exp)
40 split = sep_inserter(split, sep)
41 return tuple(add_leading_space_if_first_is_num(split, sep))
42
43
44 def refine_split_grouping(iterable):
45 """Combines lists into strings, and separates unicode numbers from ASCII"""
46 for isnum, values in iterable:
47 values = list(values)
48 # Further refine numbers into unicode and ASCII numeric characters.
49 if isnum:
50 num_grouped = group_unicode_and_ascii_numbers(values)
51 for isuni, num_values in num_grouped:
52 # If unicode, return one character at a time.
53 if isuni:
54 for u in num_values:
55 yield SplitElement(True, u, True)
56 # If ASCII, combine into a single multicharacter number.
57 else:
58 val = ''.join(num_values)
59 yield SplitElement(True, val, False)
60
61 else:
62 # If non-numeric, combine into a single string.
63 val = ''.join(values)
64 yield SplitElement(False, val, False)
65
66
67 def group_unicode_and_ascii_numbers(
68 iterable, ascii_digits=frozenset(decimals + '0123456789')
69 ):
70 """
71 Use groupby to group ASCII and unicode numeric characters.
72 Assumes all input is already all numeric characters.
73 """
74 return itertools.groupby(iterable, lambda a: a not in ascii_digits)
75
76
77 def int_splitter_iter(iterable, signed):
78 """Split the input into integers and strings."""
79 for isnum, val, isuni in iterable:
80 if isuni:
81 yield unicodedata.digit(val)
82 elif isnum:
83 yield int(val)
84 elif signed:
85 for x in try_to_read_signed_integer(iterable, val):
86 yield int(''.join(x)) if isinstance(x, list) else x
87 else:
88 yield val
89
90
91 def float_splitter_iter(iterable, signed, exp):
92 """Split the input into integers and other."""
93 weird_check = ('-inf', '-infinity', '+inf', '+infinity',
94 'inf', 'infinity', 'nan', '-nan', '+nan')
95 try_to_read_float_correctly = [
96 try_to_read_float,
97 try_to_read_float_with_exp,
98 functools.partial(try_to_read_signed_float_template,
99 key=try_to_read_float),
100 functools.partial(try_to_read_signed_float_template,
101 key=try_to_read_float_with_exp),
102 ][signed * 2 + exp * 1] # Choose the appropriate converter function.
103 for isnum, val, isuni in iterable:
104 if isuni:
105 yield unicodedata.numeric(val)
106 else:
107 for x in try_to_read_float_correctly(iterable, isnum, val):
108 if isinstance(x, list):
109 yield float(''.join(x))
110 elif x.lower().strip(' \t\n\r\f\v') in weird_check:
111 yield float(x)
112 else:
113 yield x
114
115
116 def try_to_read_signed_integer(iterable, val):
117 """
118 If the given string ends with +/-, attempt to return a signed int.
119 Otherwise, return the string as-is.
120 """
121 if val.endswith(('+', '-')):
122 next_element = next(iterable, None)
123
124 # Last element, return as-is.
125 if next_element is None:
126 yield val
127 return
128
129 # We know the next value in the sequence must be "isnum == True".
130 # We just need to handle unicode or not.
131 _, next_val, next_isuni = next_element
132
133 # If unicode, don't apply sign and just return the val as-is
134 # and convert the unicode character.
135 if next_isuni:
136 yield val
137 yield unicodedata.digit(next_val)
138
139 # If the val is *only* the sign, return only the number.
140 elif val in ('-', '+'):
141 yield [val, next_val]
142
143 # Otherwise, remove the sign from the val and apply it to the number,
144 # returning both.
145 else:
146 yield val[:-1]
147 yield [val[-1], next_val]
148
149 else:
150 yield val
151
152
153 def try_to_read_float(iterable, isnum, val):
154 """
155 Try to read a string that matches num.num and return as a float.
156 Otherwise return the input as found.
157 """
158 # Extract what is coming next.
159 next_isnum, next_val, next_isuni = iterable.peek(triple_none)
160
161 # If a non-number was given, we can only accept a decimal point.
162 if not isnum:
163
164 # If the next value is None or not a non-uni number, return as-is.
165 if next_val is None or not next_isnum or next_isuni:
166 yield val
167
168 # If this the decimal point, add it to the number and return.
169 elif val == '.':
170 next(iterable) # To progress the iterator.
171 yield [val, next_val]
172
173 # If the val ends with the decimal point, split the decimal point
174 # off the end of the string then place it to the front of the
175 # iterable so that we can use it later.
176 elif val.endswith('.'):
177 iterable.push(SplitElement(False, val[-1], False))
178 yield val[:-1]
179
180 # Otherwise, just return the val and move on.
181 else:
182 yield val
183
184 # If a number, read the number then try to get the post-decimal part.
185 else:
186
187 # If the next element is not '.', return now.
188 if next_val != '.':
189 # If the next val starts with a '.', let's add that.
190 if next_val is not None and next_val.startswith('.'):
191 next(iterable) # To progress the iterator.
192 iterable.push(SplitElement(False, next_val[1:], False))
193 yield [val, next_val[0]]
194 else:
195 yield [val]
196
197 # Recursively parse the decimal and after. If the returned
198 # value is a list, add the list to the current number.
199 # If not, just return the number with the decimal.
200 else:
201 # If the first value returned from the try_to_read_float
202 # is a list, add it to the float component list.
203 next(iterable) # To progress the iterator.
204 ret = next(try_to_read_float(iterable, next_isnum, next_val))
205 if isinstance(ret, list):
206 yield [val] + ret
207 else:
208 yield [val, next_val]
209
210
211 def try_to_read_float_with_exp(iterable, isnum, val):
212 """
213 Try to read a string that matches num.numE[+-]num and return as a float.
214 Otherwise return the input as found.
215 """
216 exp_ident = ('e', 'E', 'e-', 'E-', 'e+', 'E+')
217
218 # Start by reading the floating point part.
219 float_ret = next(try_to_read_float(iterable, isnum, val))
220
221 # Extract what is coming next.
222 next_isnum, next_val, next_isuni = iterable.peek(triple_none)
223
224 # If the float part is not a list, or the next value
225 # is not in the exponential identifier list, return it as-is.
226 if not isinstance(float_ret, list) or next_val not in exp_ident:
227 yield float_ret
228
229 # We know the next_val is an exponential identifier. See if the value
230 # after that is a non-unicode number. If so, return all as a float.
231 # If not, put the exponential identifier back on the front of the
232 # list and return the float_ret as-is.
233 else:
234 exp = SplitElement(next_isnum, next_val, next_isuni)
235 next(iterable) # To progress the iterator.
236 next_isnum, next_val, next_isuni = iterable.peek(triple_none)
237 if next_isnum and not next_isuni:
238 next(iterable) # To progress the iterator.
239 yield float_ret + [exp.val, next_val]
240 else:
241 iterable.push(exp)
242 yield float_ret
243
244
245 def try_to_read_signed_float_template(iterable, isnum, val, key):
246 """
247 Try to read a string that matches [+-]num.numE[+-]num and return as a
248 float. Otherwise return the input as found.
249 """
250 # Extract what is coming next.
251 next_isnum, next_val, next_isuni = iterable.peek(triple_none)
252
253 # If it looks like there is a sign here and the next value is a
254 # non-unicode number, try to parse that with the sign.
255 if val.endswith(('+', '-')) and next_isnum and not next_isuni:
256
257 # If this value is a sign, return the combo.
258 if val in ('+', '-'):
259 next(iterable) # To progress the iterator.
260 yield [val] + next(key(iterable, next_isnum, next_val))
261
262 # If the val ends with the sign split the sign off the end of
263 # the string then place it to the front of the iterable so that
264 # we can use it later.
265 else:
266 iterable.push(SplitElement(False, val[-1], False))
267 yield val[:-1]
268
269 # If it looks like there is a sign here and the next value is a
270 # decimal, try to parse as a decimal.
271 elif val.endswith(('+.', '-.')) and next_isnum and not next_isuni:
272
273 # Push back a zero before the decimal then parse.
274 print(val, iterable.peek())
275
276 # If this value is a sign, return the combo
277 if val[:-1] in ('+', '-'):
278 yield [val[:-1]] + next(key(iterable, False, val[-1]))
279
280 # If the val ends with the sign split the decimal the end of
281 # the string then place it to the front of the iterable so that
282 # we can use it later.
283 else:
284 iterable.push(SplitElement(False, val[-2:], False))
285 yield val[:-2]
286
287 # If no sign, pass directly to the key function.
288 else:
289 yield next(key(iterable, isnum, val))
290
291
292 def add_leading_space_if_first_is_num(iterable, sep):
293 """Check if the first element is a number, and prepend with space if so."""
294 z, peek = itertools.tee(iterable)
295 if type(next(peek, None)) in (int, long, float):
296 z = itertools.chain([sep], z)
297 del peek
298 return z
299
300
301 def sep_inserter(iterable, sep, types=frozenset((int, long, float))):
302 """Simulates the py3_safe function."""
303 pairs = pairwise(iterable)
304
305 # Prime loop by handling first pair specially.
306 try:
307 first, second = next(pairs)
308 except StopIteration:
309 return
310 if second is None: # Only one element
311 yield first
312 elif type(first) in types and type(second) in types:
313 yield first
314 yield sep
315 yield second
316 else:
317 yield first
318 yield second
319
320 # Handle all remaining pairs in loop.
321 for first, second in pairs:
322 if type(first) in types and type(second) in types:
323 yield sep
324 yield second
325
326
327 def pairwise(iterable):
328 "s -> (s0,s1), (s1,s2), (s2,s3), ..."
329 split1, split2 = itertools.tee(iterable)
330 a, b = itertools.tee(split1)
331 test1, test2 = itertools.tee(split2)
332 next(b, None)
333 if next(test1, None) is None:
334 ret = py23_zip(a, b) # Returns empty list
335 elif next(test2, None) is not None and next(test2, None) is None:
336 ret = py23_zip(a, [None]) # Return at least one value
337 else:
338 ret = py23_zip(a, b)
339 del test1, test2, split2
340 return ret
341
342
343 class peekable(object):
344 """Wrapper for an iterator to allow 1-item lookahead
345 Call ``peek()`` on the result to get the value that will next pop out of
346 ``next()``, without advancing the iterator:
347 >>> p = peekable(xrange(2))
348 >>> p.peek()
349 0
350 >>> p.next()
351 0
352 >>> p.peek()
353 1
354 >>> p.next()
355 1
356 Pass ``peek()`` a default value, and it will be returned in the case where
357 the iterator is exhausted:
358 >>> p = peekable([])
359 >>> p.peek('hi')
360 'hi'
361 If no default is provided, ``peek()`` raises ``StopIteration`` when there
362 are no items left.
363 To test whether there are more items in the iterator, examine the
364 peekable's truth value. If it is truthy, there are more items.
365 >>> assert peekable(xrange(1))
366 >>> assert not peekable([])
367 """
368 # Lowercase to blend in with itertools. The fact that it's a class is an
369 # implementation detail.
370
371 def __init__(self, iterable):
372 self._it = iter(iterable)
373
374 def __iter__(self):
375 return self
376
377 def __nonzero__(self):
378 try:
379 self.peek()
380 except StopIteration:
381 return False
382 return True
383
384 __bool__ = __nonzero__
385
386 def peek(self, default=_sentinel):
387 """Return the item that will be next returned from ``next()``.
388 Return ``default`` if there are no items left. If ``default`` is not
389 provided, raise ``StopIteration``.
390 """
391 if not hasattr(self, '_peek'):
392 try:
393 self._peek = next(self._it)
394 except StopIteration:
395 if default is _sentinel:
396 raise
397 return default
398 return self._peek
399
400 def next(self):
401 ret = self.peek()
402 try:
403 del self._peek
404 except AttributeError:
405 pass
406 return ret
407
408 __next__ = next
409
410 def push(self, value):
411 """Put an element at the front of the iterable."""
412 if hasattr(self, '_peek'):
413 self._it = itertools.chain([value, self._peek], self._it)
414 del self._peek
415 else:
416 self._it = itertools.chain([value], self._it)
55
66 import unicodedata
77 from math import isnan
8
9 from hypothesis import given
10 from hypothesis.strategies import floats, integers, text
11 from natsort.compat.fake_fastnumbers import fast_float, fast_int
812 from natsort.compat.py23 import PY_VERSION
9 from natsort.compat.fake_fastnumbers import (
10 fast_float,
11 fast_int,
12 )
13 from hypothesis import (
14 given,
15 )
16 from hypothesis.strategies import (
17 floats,
18 integers,
19 text,
20 )
2113
2214 if PY_VERSION >= 3:
2315 long = int
6759
6860
6961 def test_fast_float_returns_nan_alternate_if_nan_option_is_given():
70 assert fast_float('nan', nan=7) == 7
62 assert fast_float("nan", nan=7) == 7
7163
7264
7365 def test_fast_float_converts_float_string_to_float_example():
74 assert fast_float('45.8') == 45.8
75 assert fast_float('-45') == -45.0
76 assert fast_float('45.8e-2', key=len) == 45.8e-2
77 assert isnan(fast_float('nan'))
78 assert isnan(fast_float('+nan'))
79 assert isnan(fast_float('-NaN'))
80 assert fast_float('۱۲.۱۲') == 12.12
81 assert fast_float('-۱۲.۱۲') == -12.12
66 assert fast_float("45.8") == 45.8
67 assert fast_float("-45") == -45.0
68 assert fast_float("45.8e-2", key=len) == 45.8e-2
69 assert isnan(fast_float("nan"))
70 assert isnan(fast_float("+nan"))
71 assert isnan(fast_float("-NaN"))
72 assert fast_float("۱۲.۱۲") == 12.12
73 assert fast_float("-۱۲.۱۲") == -12.12
8274
8375
8476 @given(floats(allow_nan=False))
8779
8880
8981 def test_fast_float_leaves_string_as_is_example():
90 assert fast_float('invalid') == 'invalid'
82 assert fast_float("invalid") == "invalid"
9183
9284
9385 @given(text().filter(not_a_float).filter(bool))
9688
9789
9890 def test_fast_float_with_key_applies_to_string_example():
99 assert fast_float('invalid', key=len) == len('invalid')
91 assert fast_float("invalid", key=len) == len("invalid")
10092
10193
10294 @given(text().filter(not_a_float).filter(bool))
10597
10698
10799 def test_fast_int_leaves_float_string_as_is_example():
108 assert fast_int('45.8') == '45.8'
109 assert fast_int('nan') == 'nan'
110 assert fast_int('inf') == 'inf'
100 assert fast_int("45.8") == "45.8"
101 assert fast_int("nan") == "nan"
102 assert fast_int("inf") == "inf"
111103
112104
113105 @given(floats().filter(not_an_int))
116108
117109
118110 def test_fast_int_converts_int_string_to_int_example():
119 assert fast_int('-45') == -45
120 assert fast_int('+45') == 45
121 assert fast_int('۱۲') == 12
122 assert fast_int('-۱۲') == -12
111 assert fast_int("-45") == -45
112 assert fast_int("+45") == 45
113 assert fast_int("۱۲") == 12
114 assert fast_int("-۱۲") == -12
123115
124116
125117 @given(integers())
128120
129121
130122 def test_fast_int_leaves_string_as_is_example():
131 assert fast_int('invalid') == 'invalid'
123 assert fast_int("invalid") == "invalid"
132124
133125
134126 @given(text().filter(not_an_int).filter(bool))
137129
138130
139131 def test_fast_int_with_key_applies_to_string_example():
140 assert fast_int('invalid', key=len) == len('invalid')
132 assert fast_int("invalid", key=len) == len("invalid")
141133
142134
143135 @given(text().filter(not_an_int).filter(bool))
11 """These test the utils.py functions."""
22 from __future__ import unicode_literals
33
4 from natsort.ns_enum import ns
5 from natsort.utils import _final_data_transform_factory
4 import pytest
5 from hypothesis import example, given
6 from hypothesis.strategies import floats, integers, text
67 from natsort.compat.py23 import py23_str
7 from hypothesis import (
8 given,
9 )
10 from hypothesis.strategies import (
11 text,
12 floats,
13 integers,
14 )
8 from natsort.ns_enum import ns, ns_DUMB
9 from natsort.utils import final_data_transform_factory
1510
1611
17 # Each test has an "example" version for demonstrative purposes,
18 # and a test that uses the hypothesis module.
12 @pytest.mark.parametrize("alg", [ns.DEFAULT, ns.UNGROUPLETTERS, ns.LOCALE])
13 @given(x=text(), y=floats(allow_nan=False, allow_infinity=False) | integers())
14 @pytest.mark.usefixtures("with_locale_en_us")
15 def test_final_data_transform_factory_default(x, y, alg):
16 final_data_transform_func = final_data_transform_factory(alg, "", "::")
17 value = (x, y)
18 original_value = "".join(map(py23_str, value))
19 result = final_data_transform_func(value, original_value)
20 assert result == value
1921
2022
21 def test_final_data_transform_factory_with_iterable_returns_tuple_with_no_options_example():
22 assert _final_data_transform_factory(0, '', '')(iter([7]), '') == (7,)
23 @pytest.mark.parametrize(
24 "alg, func",
25 [
26 (ns.UNGROUPLETTERS | ns.LOCALE, lambda x: x),
27 (ns.LOCALE | ns.UNGROUPLETTERS | ns_DUMB, lambda x: x),
28 (ns.LOCALE | ns.UNGROUPLETTERS | ns.LOWERCASEFIRST, lambda x: x),
29 (
30 ns.LOCALE | ns.UNGROUPLETTERS | ns_DUMB | ns.LOWERCASEFIRST,
31 lambda x: x.swapcase(),
32 ),
33 ],
34 )
35 @given(x=text(), y=floats(allow_nan=False, allow_infinity=False) | integers())
36 @example(x="İ", y=0)
37 @pytest.mark.usefixtures("with_locale_en_us")
38 def test_final_data_transform_factory_ungroup_and_locale(x, y, alg, func):
39 final_data_transform_func = final_data_transform_factory(alg, "", "::")
40 value = (x, y)
41 original_value = "".join(map(py23_str, value))
42 result = final_data_transform_func(value, original_value)
43 if x:
44 expected = ((func(original_value[:1]),), value)
45 else:
46 expected = (("::",), value)
47 assert result == expected
2348
2449
25 @given(text())
26 def test_final_data_transform_factory_with_iterable_returns_tuple_with_no_options(x):
27 assert _final_data_transform_factory(0, '', '')(iter([x]), '') == (x,)
28 # UNGROUPLETTERS without LOCALE does nothing, as does LOCALE without UNGROUPLETTERS
29 assert _final_data_transform_factory(ns.UNGROUPLETTERS, '', '')(iter([x]), '') == _final_data_transform_factory(0, '', '')(iter([x]), '')
30 assert _final_data_transform_factory(ns.LOCALE, '', '')(iter([x]), '') == _final_data_transform_factory(0, '', '')(iter([x]), '')
31
32
33 def test_final_data_transform_factory_with_empty_tuple_returns_double_empty_tuple():
34 assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS, '', '')((), '') == ((), ())
35
36
37 def test_final_data_transform_factory_with_null_string_first_element_adds_empty_string_on_first_tuple_element():
38 assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS, '', 'xx')(('', 60), '') == (('xx',), ('', 60))
39
40
41 def test_final_data_transform_factory_returns_first_element_in_first_tuple_element_example():
42 assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS, '', '')(('this', 60), 'this60') == (('t',), ('this', 60))
43
44
45 @given(x=text().filter(bool), y=floats(allow_nan=False, allow_infinity=False) | integers())
46 def test_final_data_transform_factory_returns_first_element_in_first_tuple_element(x, y):
47 assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS, '', '')((x, y), ''.join(map(py23_str, [x, y]))) == ((x[0],), (x, y))
48
49
50 def test_final_data_transform_factory_returns_first_element_in_first_tuple_element_caseswapped_with_DUMB_and_LOWERCASEFIRST_example():
51 assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS | ns._DUMB | ns.LOWERCASEFIRST, '', '')(('this', 60), 'this60') == (('T',), ('this', 60))
52
53
54 @given(x=text().filter(bool), y=floats(allow_nan=False, allow_infinity=False) | integers())
55 def test_final_data_transform_factory_returns_first_element_in_first_tuple_element_caseswapped_with_DUMB_and_LOWERCASEFIRST(x, y):
56 assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS | ns._DUMB | ns.LOWERCASEFIRST, '', '')((x, y), ''.join(map(py23_str, [x, y]))) == ((x[0].swapcase(),), (x, y))
50 def test_final_data_transform_factory_ungroup_and_locale_empty_tuple():
51 final_data_transform_func = final_data_transform_factory(ns.UG | ns.L, "", "::")
52 assert final_data_transform_func((), "") == ((), ())
22 from __future__ import unicode_literals
33
44 import pytest
5 import locale
6 from operator import methodcaller
7 from natsort.ns_enum import ns
8 from natsort.utils import _input_string_transform_factory
5 from hypothesis import example, given
6 from hypothesis.strategies import integers, text
97 from natsort.compat.py23 import NEWPY
10 from compat.locale import (
11 load_locale,
12 has_locale_de_DE,
13 )
14 from hypothesis import (
15 given,
16 )
17 from hypothesis.strategies import (
18 text,
19 integers,
20 lists,
21 )
8 from natsort.ns_enum import ns, ns_DUMB
9 from natsort.utils import input_string_transform_factory
2210
2311
24 # Each test has an "example" version for demonstrative purposes,
25 # and a test that uses the hypothesis module.
12 def lower(x):
13 """Call the appropriate lower method for the Python version."""
14 if NEWPY:
15 return x.casefold()
16 else:
17 return x.lower()
2618
2719
28 def test_input_string_transform_factory_is_no_op_for_no_alg_options_examples():
29 x = 'feijGGAd'
30 assert _input_string_transform_factory(0)(x) is x
20 def thousands_separated_int(n):
21 """Insert thousands separators in an int."""
22 new_int = ""
23 for i, y in enumerate(reversed(n), 1):
24 new_int = y + new_int
25 # For every third digit, insert a thousands separator.
26 if i % 3 == 0 and i != len(n):
27 new_int = "," + new_int
28 return new_int
3129
3230
3331 @given(text())
3432 def test_input_string_transform_factory_is_no_op_for_no_alg_options(x):
35 assert _input_string_transform_factory(0)(x) is x
33 input_string_transform_func = input_string_transform_factory(ns.DEFAULT)
34 assert input_string_transform_func(x) is x
3635
3736
38 def test_input_string_transform_factory_performs_casefold_with_IGNORECASE_examples():
39 x = 'feijGGAd'
40 if NEWPY:
41 assert _input_string_transform_factory(ns.IGNORECASE)(x) == x.casefold()
42 else:
43 assert _input_string_transform_factory(ns.IGNORECASE)(x) == x.lower()
37 @pytest.mark.parametrize(
38 "alg, example_func",
39 [
40 (ns.IGNORECASE, lower),
41 (ns_DUMB, lambda x: x.swapcase()),
42 (ns.LOWERCASEFIRST, lambda x: x.swapcase()),
43 (ns_DUMB | ns.LOWERCASEFIRST, lambda x: x), # No-op
44 (ns.IGNORECASE | ns.LOWERCASEFIRST, lambda x: lower(x.swapcase())),
45 ],
46 )
47 @given(x=text())
48 def test_input_string_transform_factory(x, alg, example_func):
49 input_string_transform_func = input_string_transform_factory(alg)
50 assert input_string_transform_func(x) == example_func(x)
4451
4552
46 @given(text())
47 def test_input_string_transform_factory_performs_casefold_with_IGNORECASE(x):
48 if NEWPY:
49 assert _input_string_transform_factory(ns.IGNORECASE)(x) == x.casefold()
50 else:
51 assert _input_string_transform_factory(ns.IGNORECASE)(x) == x.lower()
53 @example(12543642642534980) # 12,543,642,642,534,980 => 12543642642534980
54 @given(x=integers(min_value=1000))
55 @pytest.mark.usefixtures("with_locale_en_us")
56 def test_input_string_transform_factory_cleans_thousands(x):
57 int_str = str(x).rstrip("lL")
58 thousands_int_str = thousands_separated_int(int_str)
59 assert thousands_int_str.replace(",", "") != thousands_int_str
5260
61 input_string_transform_func = input_string_transform_factory(ns.LOCALE)
62 assert input_string_transform_func(thousands_int_str) == int_str
5363
54 def test_input_string_transform_factory_performs_swapcase_with_DUMB_examples():
55 x = 'feijGGAd'
56 assert _input_string_transform_factory(ns._DUMB)(x) == x.swapcase()
57
58
59 @given(text())
60 def test_input_string_transform_factory_performs_swapcase_with_DUMB(x):
61 assert _input_string_transform_factory(ns._DUMB)(x) == x.swapcase()
62
63
64 def test_input_string_transform_factory_performs_swapcase_with_LOWERCASEFIRST_example():
65 x = 'feijGGAd'
66 assert _input_string_transform_factory(ns.LOWERCASEFIRST)(x) == x.swapcase()
67
68
69 @given(text())
70 def test_input_string_transform_factory_performs_swapcase_with_LOWERCASEFIRST(x):
71 x = 'feijGGAd'
72 assert _input_string_transform_factory(ns.LOWERCASEFIRST)(x) == x.swapcase()
73
74
75 def test_input_string_transform_factory_is_no_op_with_both_LOWERCASEFIRST_AND_DUMB_example():
76 x = 'feijGGAd'
77 assert _input_string_transform_factory(ns._DUMB | ns.LOWERCASEFIRST)(x) is x
78
79
80 @given(text())
81 def test_input_string_transform_factory_is_no_op_with_both_LOWERCASEFIRST_AND_DUMB(x):
82 assert _input_string_transform_factory(ns._DUMB | ns.LOWERCASEFIRST)(x) is x
83
84
85 def test_input_string_transform_factory_performs_swapcase_and_casefold_both_LOWERCASEFIRST_AND_IGNORECASE_example():
86 x = 'feijGGAd'
87 if NEWPY:
88 assert _input_string_transform_factory(ns.IGNORECASE | ns.LOWERCASEFIRST)(x) == x.swapcase().casefold()
89 else:
90 assert _input_string_transform_factory(ns.IGNORECASE | ns.LOWERCASEFIRST)(x) == x.swapcase().lower()
91
92
93 @given(text())
94 def test_input_string_transform_factory_performs_swapcase_and_casefold_both_LOWERCASEFIRST_AND_IGNORECASE(x):
95 if NEWPY:
96 assert _input_string_transform_factory(ns.IGNORECASE | ns.LOWERCASEFIRST)(x) == x.swapcase().casefold()
97 else:
98 assert _input_string_transform_factory(ns.IGNORECASE | ns.LOWERCASEFIRST)(x) == x.swapcase().lower()
99
100
101 def test_input_string_transform_factory_removes_thousands_separator_with_LOCALE_example():
102 load_locale('en_US')
103 x = '12,543,642,642.534,534,980' # Without FLOAT it does not account for decimal.
104 assert _input_string_transform_factory(ns.LOCALE)(x) == '12543642642.534534980'
105 x = '12,543,642,642.534,534,980' # LOCALEALPHA doesn't do anything... need LOCALENUM
106 assert _input_string_transform_factory(ns.LOCALEALPHA)(x) == '12,543,642,642.534,534,980'
107 locale.setlocale(locale.LC_ALL, str(''))
108
109
110 @given(lists(elements=integers(), min_size=4, max_size=20))
111 def test_input_string_transform_factory_removes_thousands_separator_with_LOCALE(x):
112 load_locale('en_US')
113 t = ''.join(map(methodcaller('rstrip', 'lL'), map(str, map(abs, x)))) # Remove negative signs trailing L
114 s = ''
115 for i, y in enumerate(reversed(t), 1):
116 s = y + s
117 if i % 3 == 0 and i != len(t):
118 s = ',' + s
119 assert _input_string_transform_factory(ns.LOCALE)(s) == t
120 locale.setlocale(locale.LC_ALL, str(''))
121
122
123 def test_input_string_transform_factory_removes_thousands_separator_and_is_float_aware_with_LOCALE_and_FLOAT_example():
124 x = '12,543,642,642.534,534,980'
125 assert _input_string_transform_factory(ns.LOCALE | ns.FLOAT)(x) == '12543642642.534,534980'
126
127
128 @given(lists(elements=integers(), min_size=4, max_size=20), lists(elements=integers(), min_size=4, max_size=20))
129 def test_input_string_transform_factory_removes_thousands_separator_and_is_float_aware_with_LOCALE_and_FLOAT(x, y):
130 load_locale('en_US')
131 t = ''.join(map(methodcaller('rstrip', 'lL'), map(str, map(abs, x)))) # Remove negative signs trailing L
132 s = ''
133 for i, z in enumerate(reversed(t), 1):
134 s = z + s
135 if i % 3 == 0 and i != len(t):
136 s = ',' + s
137 u = ''.join(map(methodcaller('rstrip', 'lL'), map(str, map(abs, y)))) # Remove negative signs trailing L
138 v = ''
139 for i, z in enumerate(reversed(u), 1):
140 v = z + v
141 if i % 3 == 0 and i != len(u):
142 v = ',' + v
143 # Remove all but first comma.
144 a = v.split(',', 1)
145 p = a[0] + ',' + a[1].replace(',', '')
146 assert _input_string_transform_factory(ns.LOCALE)('.'.join([s, v])) == '.'.join([t, u])
147 assert _input_string_transform_factory(ns.LOCALE | ns.FLOAT)('.'.join([s, v])) == '.'.join([t, p])
148 locale.setlocale(locale.LC_ALL, str(''))
64 # Using LOCALEALPHA does not affect numbers.
65 input_string_transform_func_no_op = input_string_transform_factory(ns.LOCALEALPHA)
66 assert input_string_transform_func_no_op(thousands_int_str) == thousands_int_str
14967
15068
15169 # These might be too much to test with hypothesis.
15270
15371
154 def test_input_string_transform_factory_leaves_invalid_thousands_separator_with_LOCALE_example():
155 load_locale('en_US')
156 x = '12,543,642642.5345,34980'
157 assert _input_string_transform_factory(ns.LOCALE)(x) == '12543,642642.5345,34980'
158 x = '12,59443,642,642.53,4534980'
159 assert _input_string_transform_factory(ns.LOCALE)(x) == '12,59443,642642.53,4534980'
160 x = '12543,642,642.5,34534980'
161 assert _input_string_transform_factory(ns.LOCALE)(x) == '12543,642642.5,34534980'
162 locale.setlocale(locale.LC_ALL, str(''))
72 @pytest.mark.parametrize(
73 "x, expected",
74 [
75 ("12,543,642642.5345,34980", "12543,642642.5345,34980"),
76 ("12,59443,642,642.53,4534980", "12,59443,642642.53,4534980"), # No change
77 ("12543,642,642.5,34534980", "12543,642642.5,34534980"),
78 ],
79 )
80 @pytest.mark.usefixtures("with_locale_en_us")
81 def test_input_string_transform_factory_handles_us_locale(x, expected):
82 input_string_transform_func = input_string_transform_factory(ns.LOCALE)
83 assert input_string_transform_func(x) == expected
16384
16485
165 # @pytest.mark.skipif(not has_locale_de_DE or dumb_sort(), reason='requires de_DE locale and working locale')
166 @pytest.mark.skipif(not has_locale_de_DE, reason='requires de_DE locale and working locale')
167 def test_input_string_transform_factory_replaces_decimal_separator_with_LOCALE_example():
168 load_locale('de_DE')
169 x = '1543,753'
170 assert _input_string_transform_factory(ns.LOCALE)(x) == '1543,753' # Does nothing without FLOAT
171 assert _input_string_transform_factory(ns.LOCALE | ns.FLOAT)(x) == '1543.753'
172 assert _input_string_transform_factory(ns.LOCALEALPHA)(x) == '1543,753' # LOCALEALPHA doesn't do anything... need LOCALENUM
173 locale.setlocale(locale.LC_ALL, str(''))
86 @pytest.mark.parametrize(
87 "alg, expected",
88 [
89 (ns.LOCALE, "1543,753"), # Does nothing without FLOAT
90 (ns.LOCALE | ns.FLOAT, "1543.753"),
91 (ns.LOCALEALPHA, "1543,753"), # LOCALEALPHA won't do anything, need LOCALENUM
92 ],
93 )
94 @pytest.mark.usefixtures("with_locale_de_de")
95 def test_input_string_transform_factory_handles_german_locale(alg, expected):
96 input_string_transform_func = input_string_transform_factory(alg)
97 assert input_string_transform_func("1543,753") == expected
17498
17599
176 # @pytest.mark.skipif(not has_locale_de_DE or dumb_sort(), reason='requires de_DE locale and working locale')
177 @pytest.mark.skipif(not has_locale_de_DE, reason='requires de_DE locale and working locale')
178 def test_input_string_transform_factory_does_not_replace_invalid_decimal_separator_with_LOCALE_example():
179 load_locale('de_DE')
180 x = '154s,t53'
181 assert _input_string_transform_factory(ns.LOCALE | ns.FLOAT)(x) == '154s,t53'
182 locale.setlocale(locale.LC_ALL, str(''))
100 @pytest.mark.usefixtures("with_locale_de_de")
101 def test_input_string_transform_factory_does_nothing_with_non_num_input():
102 input_string_transform_func = input_string_transform_factory(ns.LOCALE | ns.FLOAT)
103 expected = "154s,t53"
104 assert input_string_transform_func("154s,t53") == expected
22 Test the natsort command-line tool functions.
33 """
44 from __future__ import print_function, unicode_literals
5
56 import re
67 import sys
7 from pytest import raises
8 from compat.mock import patch, call
9 from hypothesis import (
10 given,
11 )
12 from hypothesis.strategies import (
13 integers,
14 floats,
15 lists,
16 data,
17 )
8
9 import pytest
10 from hypothesis import given
11 from hypothesis.strategies import data, floats, integers, lists
1812 from natsort.__main__ import (
13 check_filters,
14 keep_entry_range,
15 keep_entry_value,
1916 main,
2017 range_check,
21 check_filter,
22 keep_entry_range,
23 exclude_entry,
2418 sort_and_print_entries,
2519 )
2620
2721
28 def test_main_passes_default_arguments_with_no_command_line_options():
29 with patch('natsort.__main__.sort_and_print_entries') as p:
30 sys.argv[1:] = ['num-2', 'num-6', 'num-1']
31 main()
32 args = p.call_args[0][1]
33 assert not args.paths
34 assert args.filter is None
35 assert args.reverse_filter is None
36 assert args.exclude is None
37 assert not args.reverse
38 assert args.number_type == 'int'
39 assert not args.signed
40 assert args.exp
41 assert not args.locale
42
43
44 def test_main_passes_arguments_with_all_command_line_options():
45 with patch('natsort.__main__.sort_and_print_entries') as p:
46 sys.argv[1:] = ['--paths', '--reverse', '--locale',
47 '--filter', '4', '10',
48 '--reverse-filter', '100', '110',
49 '--number-type', 'float', '--noexp', '--sign',
50 '--exclude', '34', '--exclude', '35',
51 'num-2', 'num-6', 'num-1']
52 main()
53 args = p.call_args[0][1]
54 assert args.paths
55 assert args.filter == [(4.0, 10.0)]
56 assert args.reverse_filter == [(100.0, 110.0)]
57 assert args.exclude == [34, 35]
58 assert args.reverse
59 assert args.number_type == 'float'
60 assert args.signed
61 assert not args.exp
62 assert args.locale
22 def test_main_passes_default_arguments_with_no_command_line_options(mocker):
23 p = mocker.patch("natsort.__main__.sort_and_print_entries")
24 main("num-2", "num-6", "num-1")
25 args = p.call_args[0][1]
26 assert not args.paths
27 assert args.filter is None
28 assert args.reverse_filter is None
29 assert args.exclude is None
30 assert not args.reverse
31 assert args.number_type == "int"
32 assert not args.signed
33 assert args.exp
34 assert not args.locale
35
36
37 def test_main_passes_arguments_with_all_command_line_options(mocker):
38 arguments = ["--paths", "--reverse", "--locale"]
39 arguments.extend(["--filter", "4", "10"])
40 arguments.extend(["--reverse-filter", "100", "110"])
41 arguments.extend(["--number-type", "float"])
42 arguments.extend(["--noexp", "--sign"])
43 arguments.extend(["--exclude", "34"])
44 arguments.extend(["--exclude", "35"])
45 arguments.extend(["num-2", "num-6", "num-1"])
46 p = mocker.patch("natsort.__main__.sort_and_print_entries")
47 main(*arguments)
48 args = p.call_args[0][1]
49 assert args.paths
50 assert args.filter == [(4.0, 10.0)]
51 assert args.reverse_filter == [(100.0, 110.0)]
52 assert args.exclude == [34, 35]
53 assert args.reverse
54 assert args.number_type == "float"
55 assert args.signed
56 assert not args.exp
57 assert args.locale
6358
6459
6560 class Args:
6661 """A dummy class to simulate the argparse Namespace object"""
62
6763 def __init__(self, filt, reverse_filter, exclude, as_path, reverse):
6864 self.filter = filt
6965 self.reverse_filter = reverse_filter
7066 self.exclude = exclude
7167 self.reverse = reverse
72 self.number_type = 'float'
68 self.number_type = "float"
7369 self.signed = True
7470 self.exp = True
7571 self.paths = as_path
7672 self.locale = 0
7773
78 entries = ['tmp/a57/path2',
79 'tmp/a23/path1',
80 'tmp/a1/path1',
81 'tmp/a1 (1)/path1',
82 'tmp/a130/path1',
83 'tmp/a64/path1',
84 'tmp/a64/path2']
85
86 mock_print = '__builtin__.print' if sys.version[0] == '2' else 'builtins.print'
87
88
89 def test_sort_and_print_entries_uses_default_algorithm_with_all_options_false():
90 with patch(mock_print) as p:
91 # tmp/a1 (1)/path1
92 # tmp/a1/path1
93 # tmp/a23/path1
94 # tmp/a57/path2
95 # tmp/a64/path1
96 # tmp/a64/path2
97 # tmp/a130/path1
98 sort_and_print_entries(entries, Args(None, None, False, False, False))
99 e = [call(entries[i]) for i in [3, 2, 1, 0, 5, 6, 4]]
100 p.assert_has_calls(e)
101
102
103 def test_sort_and_print_entries_uses_PATH_algorithm_with_path_option_true_to_properly_sort_OS_generated_path_names():
104 with patch(mock_print) as p:
105 # tmp/a1/path1
106 # tmp/a1 (1)/path1
107 # tmp/a23/path1
108 # tmp/a57/path2
109 # tmp/a64/path1
110 # tmp/a64/path2
111 # tmp/a130/path1
112 sort_and_print_entries(entries, Args(None, None, False, True, False))
113 e = [call(entries[i]) for i in [2, 3, 1, 0, 5, 6, 4]]
114 p.assert_has_calls(e)
115
116
117 def test_sort_and_print_entries_keeps_only_paths_between_of_20_to_100_with_filter_option():
118 with patch(mock_print) as p:
119 # tmp/a23/path1
120 # tmp/a57/path2
121 # tmp/a64/path1
122 # tmp/a64/path2
123 sort_and_print_entries(entries, Args([(20, 100)], None, False, False, False))
124 e = [call(entries[i]) for i in [1, 0, 5, 6]]
125 p.assert_has_calls(e)
126
127
128 def test_sort_and_print_entries_excludes_paths_between_of_20_to_100_with_reverse_filter_option():
129 with patch(mock_print) as p:
130 # tmp/a1/path1
131 # tmp/a1 (1)/path1
132 # tmp/a130/path1
133 sort_and_print_entries(entries, Args(None, [(20, 100)], False, True, False))
134 e = [call(entries[i]) for i in [2, 3, 4]]
135 p.assert_has_calls(e)
136
137
138 def test_sort_and_print_entries_excludes_paths_23_or_130_with_exclude_option_list():
139 with patch(mock_print) as p:
140 # tmp/a1/path1
141 # tmp/a1 (1)/path1
142 # tmp/a57/path2
143 # tmp/a64/path1
144 # tmp/a64/path2
145 sort_and_print_entries(entries, Args(None, None, [23, 130], True, False))
146 e = [call(entries[i]) for i in [2, 3, 0, 5, 6]]
147 p.assert_has_calls(e)
148
149
150 def test_sort_and_print_entries_reverses_order_with_reverse_option():
151 with patch(mock_print) as p:
152 # tmp/a130/path1
153 # tmp/a64/path2
154 # tmp/a64/path1
155 # tmp/a57/path2
156 # tmp/a23/path1
157 # tmp/a1 (1)/path1
158 # tmp/a1/path1
159 sort_and_print_entries(entries, Args(None, None, False, True, True))
160 e = [call(entries[i]) for i in reversed([2, 3, 1, 0, 5, 6, 4])]
161 p.assert_has_calls(e)
74
75 mock_print = "__builtin__.print" if sys.version[0] == "2" else "builtins.print"
76
77 entries = [
78 "tmp/a57/path2",
79 "tmp/a23/path1",
80 "tmp/a1/path1",
81 "tmp/a1 (1)/path1",
82 "tmp/a130/path1",
83 "tmp/a64/path1",
84 "tmp/a64/path2",
85 ]
86
87
88 @pytest.mark.parametrize(
89 "options, order",
90 [
91 # Defaults, all options false
92 # tmp/a1 (1)/path1
93 # tmp/a1/path1
94 # tmp/a23/path1
95 # tmp/a57/path2
96 # tmp/a64/path1
97 # tmp/a64/path2
98 # tmp/a130/path1
99 ([None, None, False, False, False], [3, 2, 1, 0, 5, 6, 4]),
100 # Path option True
101 # tmp/a1/path1
102 # tmp/a1 (1)/path1
103 # tmp/a23/path1
104 # tmp/a57/path2
105 # tmp/a64/path1
106 # tmp/a64/path2
107 # tmp/a130/path1
108 ([None, None, False, True, False], [2, 3, 1, 0, 5, 6, 4]),
109 # Filter option keeps only within range
110 # tmp/a23/path1
111 # tmp/a57/path2
112 # tmp/a64/path1
113 # tmp/a64/path2
114 ([[(20, 100)], None, False, False, False], [1, 0, 5, 6]),
115 # Reverse filter, exclude in range
116 # tmp/a1/path1
117 # tmp/a1 (1)/path1
118 # tmp/a130/path1
119 ([None, [(20, 100)], False, True, False], [2, 3, 4]),
120 # Exclude given values with exclude list
121 # tmp/a1/path1
122 # tmp/a1 (1)/path1
123 # tmp/a57/path2
124 # tmp/a64/path1
125 # tmp/a64/path2
126 ([None, None, [23, 130], True, False], [2, 3, 0, 5, 6]),
127 # Reverse order
128 # tmp/a130/path1
129 # tmp/a64/path2
130 # tmp/a64/path1
131 # tmp/a57/path2
132 # tmp/a23/path1
133 # tmp/a1 (1)/path1
134 # tmp/a1/path1
135 ([None, None, False, True, True], reversed([2, 3, 1, 0, 5, 6, 4])),
136 ],
137 )
138 def test_sort_and_print_entries(options, order, mocker):
139 p = mocker.patch(mock_print)
140 sort_and_print_entries(entries, Args(*options))
141 e = [mocker.call(entries[i]) for i in order]
142 p.assert_has_calls(e)
162143
163144
164145 # Each test has an "example" version for demonstrative purposes,
165146 # and a test that uses the hypothesis module.
166147
167 def test_range_check_returns_range_as_is_but_with_floats_if_first_is_less_than_second_example():
148
149 def test_range_check_returns_range_as_is_but_with_floats_example():
168150 assert range_check(10, 11) == (10.0, 11.0)
169151 assert range_check(6.4, 30) == (6.4, 30.0)
170152
171153
172 @given(x=integers(), data=data()) # Defer data selection for y till test is run.
173 def test_range_check_returns_range_as_is_but_with_floats_if_first_is_less_than_second(x, data):
154 @given(x=floats(allow_nan=False, min_value=-1E8, max_value=1E8) | integers(), d=data())
155 def test_range_check_returns_range_as_is_if_first_is_less_than_second(x, d):
174156 # Pull data such that the first is less than the second.
175 y = data.draw(integers(min_value=x + 1))
157 if isinstance(x, float):
158 y = d.draw(floats(min_value=x + 1.0, max_value=1E9, allow_nan=False))
159 else:
160 y = d.draw(integers(min_value=x + 1))
176161 assert range_check(x, y) == (x, y)
177162
178163
179 @given(x=floats(allow_nan=False, min_value=-1E8, max_value=1E8), data=data()) # Defer data selection for y till test is run.
180 def test_range_check_returns_range_as_is_but_with_floats_if_first_is_less_than_second2(x, data):
181 # Pull data such that the first is less than the second.
182 y = data.draw(floats(min_value=x + 1.0, max_value=1E9, allow_nan=False))
183 assert range_check(x, y) == (x, y)
184
185
186 def test_range_check_raises_ValueError_if_second_is_less_than_first_example():
187 with raises(ValueError) as err:
164 def test_range_check_raises_value_error_if_second_is_less_than_first_example():
165 with pytest.raises(ValueError, match="low >= high"):
188166 range_check(7, 2)
189 assert str(err.value) == 'low >= high'
190
191
192 @given(x=floats(allow_nan=False), data=data()) # Defer data selection for y till test is run.
193 def test_range_check_raises_ValueError_if_second_is_less_than_first(x, data):
167
168
169 @given(x=floats(allow_nan=False), d=data())
170 def test_range_check_raises_value_error_if_second_is_less_than_first(x, d):
194171 # Pull data such that the first is greater than or equal to the second.
195 y = data.draw(floats(max_value=x, allow_nan=False))
196 with raises(ValueError) as err:
172 y = d.draw(floats(max_value=x, allow_nan=False))
173 with pytest.raises(ValueError, match="low >= high"):
197174 range_check(x, y)
198 assert str(err.value) == 'low >= high'
199
200
201 def test_check_filter_returns_None_if_filter_evaluates_to_False():
202 assert check_filter(()) is None
203 assert check_filter(False) is None
204 assert check_filter(None) is None
205
206
207 def test_check_filter_returns_input_as_is_if_filter_is_valid_example():
208 assert check_filter([(6, 7)]) == [(6, 7)]
209 assert check_filter([(6, 7), (2, 8)]) == [(6, 7), (2, 8)]
210
211
212 @given(x=lists(integers(), min_size=1), data=data()) # Defer data selection for y till test is run.
213 def test_check_filter_returns_input_as_is_if_filter_is_valid(x, data):
214 y = [data.draw(integers(min_value=val + 1)) for val in x] # ensure y is element-wise greater than x
215 assert check_filter(list(zip(x, y))) == [(i, j) for i, j in zip(x, y)]
216
217
218 def test_check_filter_raises_ValueError_if_filter_is_invalid_example():
219 with raises(ValueError) as err:
220 check_filter([(7, 2)])
221 assert str(err.value) == 'Error in --filter: low >= high'
222
223
224 @given(x=lists(integers(), min_size=1), data=data()) # Defer data selection for y till test is run.
225 def test_check_filter_raises_ValueError_if_filter_is_invalid(x, data):
226 y = [data.draw(integers(max_value=val)) for val in x] # ensure y is element-wise less than or equal to x
227 with raises(ValueError) as err:
228 check_filter(list(zip(x, y)))
229 assert str(err.value) == 'Error in --filter: low >= high'
230
231
232 def test_keep_entry_range_returns_True_if_any_portion_of_input_is_between_the_range_bounds_example():
233 assert keep_entry_range('a56b23c89', [0], [100], int, re.compile(r'\d+'))
234
235
236 def test_keep_entry_range_returns_True_if_any_portion_of_input_is_between_any_range_bounds_example():
237 assert keep_entry_range('a56b23c89', [1, 88], [20, 90], int, re.compile(r'\d+'))
238
239
240 def test_keep_entry_range_returns_False_if_no_portion_of_input_is_between_the_range_bounds_example():
241 assert not keep_entry_range('a56b23c89', [1], [20], int, re.compile(r'\d+'))
242
243
244 def test_exclude_entry_returns_True_if_exlcude_parameters_are_not_in_input_example():
245 assert exclude_entry('a56b23c89', [100, 45], int, re.compile(r'\d+'))
246
247
248 def test_exclude_entry_returns_False_if_exlcude_parameters_are_in_input_example():
249 assert not exclude_entry('a56b23c89', [23], int, re.compile(r'\d+'))
175
176
177 def test_check_filters_returns_none_if_filter_evaluates_to_false():
178 assert check_filters(()) is None
179 assert check_filters(False) is None
180 assert check_filters(None) is None
181
182
183 def test_check_filters_returns_input_as_is_if_filter_is_valid_example():
184 assert check_filters([(6, 7)]) == [(6, 7)]
185 assert check_filters([(6, 7), (2, 8)]) == [(6, 7), (2, 8)]
186
187
188 @given(x=lists(integers(), min_size=1), d=data())
189 def test_check_filters_returns_input_as_is_if_filter_is_valid(x, d):
190 # ensure y is element-wise greater than x
191 y = [d.draw(integers(min_value=val + 1)) for val in x]
192 assert check_filters(list(zip(x, y))) == [(i, j) for i, j in zip(x, y)]
193
194
195 def test_check_filters_raises_value_error_if_filter_is_invalid_example():
196 with pytest.raises(ValueError, match="Error in --filter: low >= high"):
197 check_filters([(7, 2)])
198
199
200 @given(x=lists(integers(), min_size=1), d=data())
201 def test_check_filters_raises_value_error_if_filter_is_invalid(x, d):
202 # ensure y is element-wise less than or equal to x
203 y = [d.draw(integers(max_value=val)) for val in x]
204 with pytest.raises(ValueError, match="Error in --filter: low >= high"):
205 check_filters(list(zip(x, y)))
206
207
208 @pytest.mark.parametrize(
209 "lows, highs, truth",
210 # 1. Any portion is between the bounds => True.
211 # 2. Any portion is between any bounds => True.
212 # 3. No portion is between the bounds => False.
213 [([0], [100], True), ([1, 88], [20, 90], True), ([1], [20], False)],
214 )
215 def test_keep_entry_range(lows, highs, truth):
216 assert keep_entry_range("a56b23c89", lows, highs, int, re.compile(r"\d+")) is truth
217
218
219 # 1. Values not in entry => True. 2. Values in entry => False.
220 @pytest.mark.parametrize("values, truth", [([100, 45], True), ([23], False)])
221 def test_keep_entry_value(values, truth):
222 assert keep_entry_value("a56b23c89", values, int, re.compile(r"\d+")) is truth
33
44 Note that these tests are only relevant for Python version < 3.
55 """
6 import sys
76 from functools import partial
8 from compat.mock import patch
97
108 import pytest
119 from hypothesis import given
1210 from hypothesis.strategies import floats, integers, lists
13
1411 from natsort import ns
15
16 from natsort.compat.py23 import py23_cmp
17
18 PY_VERSION = float(sys.version[:3])
12 from natsort.compat.py23 import PY_VERSION, py23_cmp
1913
2014 if PY_VERSION < 3:
2115 from natsort import natcmp
2317
2418 class Comparable(object):
2519 """Stub class for testing natcmp functionality."""
20
2621 def __init__(self, value):
2722 self.value = value
2823
3025 return natcmp(self.value, other.value)
3126
3227
33 @pytest.mark.skipif(PY_VERSION >= 3.0, reason='cmp() deprecated in Python 3')
34 def test__classes_can_be_compared():
35 one = Comparable("1")
36 two = Comparable("2")
37 another_two = Comparable("2")
38 ten = Comparable("10")
28 @pytest.mark.skipif(PY_VERSION >= 3.0, reason="cmp() deprecated in Python 3")
29 class TestNatCmp:
3930
40 assert ten > two == another_two > one
31 def test_classes_can_be_compared(self):
32 one = Comparable("1")
33 two = Comparable("2")
34 another_two = Comparable("2")
35 ten = Comparable("10")
36 assert ten > two == another_two > one
4137
38 def test_keys_are_being_cached(self, mocker):
39 natcmp.cached_keys = {}
40 assert len(natcmp.cached_keys) == 0
41 natcmp(0, 0)
42 assert len(natcmp.cached_keys) == 1
43 natcmp(0, 0)
44 assert len(natcmp.cached_keys) == 1
4245
43 @pytest.mark.skipif(PY_VERSION >= 3.0, reason='cmp() deprecated in Python 3')
44 def test__keys_are_being_cached():
45 natcmp.cached_keys = {}
46 assert len(natcmp.cached_keys) == 0
47 natcmp(0, 0)
48 assert len(natcmp.cached_keys) == 1
49 natcmp(0, 0)
50 assert len(natcmp.cached_keys) == 1
46 with mocker.patch("natsort.compat.locale.dumb_sort", return_value=False):
47 natcmp(0, 0, alg=ns.L)
48 assert len(natcmp.cached_keys) == 2
49 natcmp(0, 0, alg=ns.L)
50 assert len(natcmp.cached_keys) == 2
5151
52 with patch('natsort.compat.locale.dumb_sort', return_value=False):
53 natcmp(0, 0, alg=ns.L)
54 assert len(natcmp.cached_keys) == 2
55 natcmp(0, 0, alg=ns.L)
56 assert len(natcmp.cached_keys) == 2
52 with mocker.patch("natsort.compat.locale.dumb_sort", return_value=True):
53 natcmp(0, 0, alg=ns.L)
54 assert len(natcmp.cached_keys) == 3
55 natcmp(0, 0, alg=ns.L)
56 assert len(natcmp.cached_keys) == 3
5757
58 with patch('natsort.compat.locale.dumb_sort', return_value=True):
59 natcmp(0, 0, alg=ns.L)
60 assert len(natcmp.cached_keys) == 3
61 natcmp(0, 0, alg=ns.L)
62 assert len(natcmp.cached_keys) == 3
58 def test_illegal_algorithm_raises_error(self):
59 with pytest.raises(ValueError):
60 natcmp(0, 0, alg="Just random stuff")
6361
62 def test_classes_can_utilize_max_or_min(self):
63 comparables = [Comparable(i) for i in range(10)]
6464
65 @pytest.mark.skipif(PY_VERSION >= 3.0, reason='cmp() deprecated in Python 3')
66 def test__illegal_algorithm_raises_error():
67 try:
68 natcmp(0, 0, alg="Just random stuff")
69 assert False
65 assert max(comparables) == comparables[-1]
66 assert min(comparables) == comparables[0]
7067
71 except ValueError:
72 assert True
68 @given(integers(), integers())
69 def test_natcmp_works_the_same_for_integers_as_cmp(self, x, y):
70 assert py23_cmp(x, y) == natcmp(x, y)
7371
74 except Exception:
75 assert False
72 @given(floats(allow_nan=False), floats(allow_nan=False))
73 def test_natcmp_works_the_same_for_floats_as_cmp(self, x, y):
74 assert py23_cmp(x, y) == natcmp(x, y)
7675
76 @given(lists(elements=integers()))
77 def test_sort_strings_with_numbers(self, a_list):
78 strings = [str(var) for var in a_list]
79 # noinspection PyArgumentList
80 natcmp_sorted = sorted(strings, cmp=partial(natcmp, alg=ns.SIGNED))
7781
78 @pytest.mark.skipif(PY_VERSION >= 3.0, reason='cmp() deprecated in Python 3')
79 def test__classes_can_utilize_max_or_min():
80 comparables = [Comparable(i) for i in range(10)]
81
82 assert max(comparables) == comparables[-1]
83 assert min(comparables) == comparables[0]
84
85
86 @pytest.mark.skipif(PY_VERSION >= 3.0, reason='cmp() deprecated in Python 3')
87 @given(integers(), integers())
88 def test__natcmp_works_the_same_for_integers_as_cmp(x, y):
89 assert py23_cmp(x, y) == natcmp(x, y)
90
91
92 @pytest.mark.skipif(PY_VERSION >= 3.0, reason='cmp() deprecated in Python 3')
93 @given(floats(allow_nan=False), floats(allow_nan=False))
94 def test__natcmp_works_the_same_for_floats_as_cmp(x, y):
95 assert py23_cmp(x, y) == natcmp(x, y)
96
97
98 @pytest.mark.skipif(PY_VERSION >= 3.0, reason='cmp() deprecated in Python 3')
99 @given(lists(elements=integers()))
100 def test_sort_strings_with_numbers(a_list):
101 strings = [str(var) for var in a_list]
102 natcmp_sorted = sorted(strings, cmp=partial(natcmp, alg=ns.SIGNED))
103
104 assert sorted(a_list) == [int(var) for var in natcmp_sorted]
82 assert sorted(a_list) == [int(var) for var in natcmp_sorted]
22 from __future__ import unicode_literals
33
44 import pytest
5 from natsort.compat.py23 import PY_VERSION
6 from natsort.ns_enum import ns
7 from natsort.utils import (
8 _natsort_key,
9 _regex_chooser,
10 _parse_string_factory,
11 _parse_path_factory,
12 _parse_number_factory,
13 _parse_bytes_factory,
14 _input_string_transform_factory,
15 _string_component_transform_factory,
16 _final_data_transform_factory,
17 )
18 from hypothesis import (
19 given,
20 )
21 from hypothesis.strategies import (
22 lists,
23 text,
24 floats,
25 integers,
26 binary,
27 )
5 from hypothesis import given
6 from hypothesis.strategies import binary, floats, integers, lists, text
7 from natsort.compat.py23 import PY_VERSION, py23_str
8 from natsort.utils import natsort_key
289
2910 if PY_VERSION >= 3:
3011 long = int
3112
3213
33 regex = _regex_chooser[ns.INT]
34 pre = _input_string_transform_factory(ns.INT)
35 post = _string_component_transform_factory(ns.INT)
36 after = _final_data_transform_factory(ns.INT, '', '')
37 string_func = _parse_string_factory(ns.INT, '', regex.split, pre, post, after)
38 bytes_func = _parse_bytes_factory(ns.INT)
39 num_func = _parse_number_factory(ns.INT, '', '')
14 def str_func(x):
15 if isinstance(x, py23_str):
16 return x
17 else:
18 raise TypeError("Not a str!")
4019
4120
42 def test__natsort_key_with_numeric_input_and_PATH_returns_number_in_nested_tuple():
43 # It gracefully handles as_path for numeric input by putting an extra tuple around it
44 # so it will sort against the other as_path results.
45 sfunc = _parse_path_factory(string_func)
46 bytes_func = _parse_bytes_factory(ns.PATH)
47 num_func = _parse_number_factory(ns.PATH, '', '')
48 assert _natsort_key(10, None, sfunc, bytes_func, num_func) == (('', 10),)
49
50
51 @pytest.mark.skipif(PY_VERSION < 3, reason='only valid on python3')
52 def test__natsort_key_with_bytes_input_and_PATH_returns_number_in_nested_tuple():
53 # It gracefully handles as_path for numeric input by putting an extra tuple around it
54 # so it will sort against the other as_path results.
55 sfunc = _parse_path_factory(string_func)
56 bytes_func = _parse_bytes_factory(ns.PATH)
57 num_func = _parse_number_factory(ns.PATH, '', '')
58 assert _natsort_key(b'/hello/world', None, sfunc, bytes_func, num_func) == ((b'/hello/world',),)
59
60
61 def test__natsort_key_with_tuple_of_paths_and_PATH_returns_triply_nested_tuple():
62 # PATH also handles recursion well.
63 sfunc = _parse_path_factory(string_func)
64 bytes_func = _parse_bytes_factory(ns.PATH)
65 num_func = _parse_number_factory(ns.PATH, '', '')
66 assert _natsort_key(('/Folder', '/Folder (1)'), None, sfunc, bytes_func, num_func) == ((('/',), ('Folder',)), (('/',), ('Folder (', 1, ')')))
67
68
69 # The remaining tests provide no examples, just hypothesis tests.
70 # They only confirm that _natsort_key uses the above building blocks.
21 def fail(_):
22 raise AssertionError("This should never be reached!")
7123
7224
7325 @given(floats(allow_nan=False) | integers())
74 def test__natsort_key_with_numeric_input_takes_number_path(x):
75 assert _natsort_key(x, None, string_func, bytes_func, num_func) == num_func(x)
26 def test_natsort_key_with_numeric_input_takes_number_path(x):
27 assert natsort_key(x, None, str_func, fail, lambda y: y) is x
7628
7729
78 @pytest.mark.skipif(PY_VERSION < 3, reason='only valid on python3')
30 @pytest.mark.skipif(PY_VERSION < 3, reason="only valid on python3")
7931 @given(binary().filter(bool))
80 def test__natsort_key_with_bytes_input_takes_bytes_path(x):
81 assert _natsort_key(x, None, string_func, bytes_func, num_func) == bytes_func(x)
32 def test_natsort_key_with_bytes_input_takes_bytes_path(x):
33 assert natsort_key(x, None, str_func, lambda y: y, fail) is x
8234
8335
84 @given(lists(elements=floats(allow_nan=False) | text() | integers(), min_size=1, max_size=10))
85 def test__natsort_key_with_text_input_takes_string_path(x):
86 s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x)
87 assert _natsort_key(s, None, string_func, bytes_func, num_func) == string_func(s)
36 @given(text())
37 def test_natsort_key_with_text_input_takes_string_path(x):
38 assert natsort_key(x, None, str_func, fail, fail) is x
8839
8940
9041 @given(lists(elements=text(), min_size=1, max_size=10))
91 def test__natsort_key_with_nested_input_takes_nested_path(x):
92 assert _natsort_key(x, None, string_func, bytes_func, num_func) == tuple(string_func(s) for s in x)
42 def test_natsort_key_with_nested_input_takes_nested_path(x):
43 assert natsort_key(x, None, str_func, fail, fail) == tuple(x)
9344
9445
9546 @given(text())
96 def test__natsort_key_with_key_argument_applies_key_before_processing(x):
97 assert _natsort_key(x, len, string_func, bytes_func, num_func) == num_func(len(x))
47 def test_natsort_key_with_key_argument_applies_key_before_processing(x):
48 assert natsort_key(x, len, str_func, fail, lambda y: y) == len(x)
22 Here are a collection of examples of how this module can be used.
33 See the README or the natsort homepage for more details.
44 """
5 from __future__ import unicode_literals, print_function
5 from __future__ import print_function, unicode_literals
66
7 import warnings
8 import locale
9 from pytest import raises
10 from natsort import (
11 natsorted,
12 natsort_key,
13 natsort_keygen,
14 ns,
15 )
7 import pytest
8 from natsort import natsort_key, natsort_keygen, natsorted, ns
9 from natsort.compat.locale import get_strxfrm, null_string_locale
1610 from natsort.compat.py23 import PY_VERSION
17 from natsort.compat.locale import (
18 null_string_locale,
19 get_strxfrm,
20 )
21 from compat.mock import patch
22 from compat.locale import load_locale
23
24 INPUT = ['6A-5.034e+1', '/Folder (1)/Foo', 56.7]
2511
2612
27 def test_natsort_key_public_raises_DeprecationWarning_when_called():
28 # But it raises a deprecation warning
29 with warnings.catch_warnings(record=True) as w:
30 warnings.simplefilter("always")
31 assert natsort_key('a-5.034e2') == ('a-', 5, '.', 34, 'e', 2)
32 assert len(w) == 1
33 assert "natsort_key is deprecated as of 3.4.0, please use natsort_keygen" in str(w[-1].message)
34 # It is called for each element in a list when sorting
35 with warnings.catch_warnings(record=True) as w:
36 warnings.simplefilter("always")
37 a = ['a2', 'a5', 'a9', 'a1', 'a4', 'a10', 'a6']
38 a.sort(key=natsort_key)
39 assert len(w) == 7
13 @pytest.fixture
14 def arbitrary_input():
15 return ["6A-5.034e+1", "/Folder (1)/Foo", 56.7]
4016
4117
42 def test_natsort_keygen_with_invalid_alg_input_raises_ValueError():
43 # Invalid arguments give the correct response
44 with raises(ValueError) as err:
45 natsort_keygen(None, '1')
46 assert str(err.value) == "natsort_keygen: 'alg' argument must be from the enum 'ns', got 1"
18 @pytest.fixture
19 def bytes_input():
20 return b"6A-5.034e+1"
4721
4822
49 def test_natsort_keygen_returns_natsort_key_that_parses_input():
50 a = 'a-5.034e1'
51 assert natsort_keygen()(a) == ('a-', 5, '.', 34, 'e', 1)
52 assert natsort_keygen(alg=ns.F | ns.S)(a) == ('a', -50.34)
23 def test_natsort_keygen_demonstration():
24 original_list = ["a50", "a51.", "a50.31", "a50.4", "a5.034e1", "a50.300"]
25 copy_of_list = original_list[:]
26 original_list.sort(key=natsort_keygen(alg=ns.F))
27 # natsorted uses the output of natsort_keygen under the hood.
28 assert original_list == natsorted(copy_of_list, alg=ns.F)
5329
5430
55 def test_natsort_keygen_returns_key_that_can_be_used_to_sort_list_in_place_with_same_result_as_natsorted():
56 a = ['a50', 'a51.', 'a50.31', 'a50.4', 'a5.034e1', 'a50.300']
57 b = a[:]
58 a.sort(key=natsort_keygen(alg=ns.F))
59 assert a == natsorted(b, alg=ns.F)
31 def test_natsort_key_public():
32 assert natsort_key("a-5.034e2") == ("a-", 5, ".", 34, "e", 2)
6033
6134
62 def test_natsort_keygen_splits_input_with_defaults():
63 assert natsort_keygen()(INPUT) == (('', 6, 'A-', 5, '.', 34, 'e+', 1), ('/Folder (', 1, ')/Foo'), ('', 56.7))
64 if PY_VERSION >= 3: assert natsort_keygen()(b'6A-5.034e+1') == (b'6A-5.034e+1',)
35 def test_natsort_keygen_with_invalid_alg_input_raises_value_error():
36 # Invalid arguments give the correct response
37 with pytest.raises(ValueError, match="'alg' argument"):
38 natsort_keygen(None, "1")
6539
6640
67 def test_natsort_keygen_splits_input_with_real():
68 assert natsort_keygen(alg=ns.R)(INPUT) == (('', 6.0, 'A', -50.34), ('/Folder (', 1.0, ')/Foo'), ('', 56.7))
69 if PY_VERSION >= 3: assert natsort_keygen(alg=ns.R)(b'6A-5.034e+1') == (b'6A-5.034e+1',)
41 @pytest.mark.parametrize(
42 "alg, expected",
43 [(ns.DEFAULT, ("a-", 5, ".", 34, "e", 1)), (ns.FLOAT | ns.SIGNED, ("a", -50.34))],
44 )
45 def test_natsort_keygen_returns_natsort_key_that_parses_input(alg, expected):
46 ns_key = natsort_keygen(alg=alg)
47 assert ns_key("a-5.034e1") == expected
7048
7149
72 def test_natsort_keygen_splits_input_with_lowercasefirst_noexp_float():
73 assert natsort_keygen(alg=ns.LF | ns.F | ns.N)(INPUT) == (('', 6.0, 'a-', 5.034, 'E+', 1.0), ('/fOLDER (', 1.0, ')/fOO'), ('', 56.7))
74 if PY_VERSION >= 3: assert natsort_keygen(alg=ns.LF | ns.F | ns.N)(b'6A-5.034e+1') == (b'6A-5.034e+1',)
50 @pytest.mark.parametrize(
51 "alg, expected",
52 [
53 (
54 ns.DEFAULT,
55 (("", 6, "A-", 5, ".", 34, "e+", 1), ("/Folder (", 1, ")/Foo"), ("", 56.7)),
56 ),
57 (
58 ns.IGNORECASE,
59 (("", 6, "a-", 5, ".", 34, "e+", 1), ("/folder (", 1, ")/foo"), ("", 56.7)),
60 ),
61 (ns.REAL, (("", 6.0, "A", -50.34), ("/Folder (", 1.0, ")/Foo"), ("", 56.7))),
62 (
63 ns.LOWERCASEFIRST | ns.FLOAT | ns.NOEXP,
64 (
65 ("", 6.0, "a-", 5.034, "E+", 1.0),
66 ("/fOLDER (", 1.0, ")/fOO"),
67 ("", 56.7),
68 ),
69 ),
70 (
71 ns.PATH | ns.GROUPLETTERS,
72 (
73 (("", 6, "aA--", 5, "..", 34, "ee++", 1),),
74 (("//",), ("fFoollddeerr ((", 1, "))"), ("fFoooo",)),
75 (("", 56.7),),
76 ),
77 ),
78 ],
79 )
80 def test_natsort_keygen_handles_arbitrary_input(arbitrary_input, alg, expected):
81 ns_key = natsort_keygen(alg=alg)
82 assert ns_key(arbitrary_input) == expected
7583
7684
77 def test_natsort_keygen_splits_input_with_locale():
78 load_locale('en_US')
79 strxfrm = get_strxfrm()
80 with patch('natsort.compat.locale.dumb_sort', return_value=False):
81 assert natsort_keygen(alg=ns.L)(INPUT) == ((null_string_locale, 6, strxfrm('A-'), 5, strxfrm('.'), 34, strxfrm('e+'), 1), (strxfrm('/Folder ('), 1, strxfrm(')/Foo')), (null_string_locale, 56.7))
82 with patch('natsort.compat.locale.dumb_sort', return_value=True):
83 assert natsort_keygen(alg=ns.L)(INPUT) == ((null_string_locale, 6, strxfrm('aa--'), 5, strxfrm('..'), 34, strxfrm('eE++'), 1), (strxfrm('//ffoOlLdDeErR (('), 1, strxfrm('))//ffoOoO')), (null_string_locale, 56.7))
84 if PY_VERSION >= 3: assert natsort_keygen(alg=ns.LA)(b'6A-5.034e+1') == (b'6A-5.034e+1',)
85 locale.setlocale(locale.LC_ALL, str(''))
85 @pytest.mark.parametrize(
86 "alg, expected",
87 [
88 (ns.DEFAULT, (b"6A-5.034e+1",)),
89 (ns.IGNORECASE, (b"6a-5.034e+1",)),
90 (ns.REAL, (b"6A-5.034e+1",)),
91 (ns.LOWERCASEFIRST | ns.FLOAT | ns.NOEXP, (b"6A-5.034e+1",)),
92 (ns.PATH | ns.GROUPLETTERS, ((b"6A-5.034e+1",),)),
93 ],
94 )
95 @pytest.mark.skipif(PY_VERSION < 3.0, reason="special bytes handling only on Python3")
96 def test_natsort_keygen_handles_bytes_input(bytes_input, alg, expected):
97 ns_key = natsort_keygen(alg=alg)
98 assert ns_key(bytes_input) == expected
8699
87100
88 def test_natsort_keygen_splits_input_with_locale_and_capitalfirst():
89 load_locale('en_US')
101 @pytest.mark.parametrize(
102 "alg, expected, is_dumb",
103 [
104 (
105 ns.LOCALE,
106 (
107 (null_string_locale, 6, "A-", 5, ".", 34, "e+", 1),
108 ("/Folder (", 1, ")/Foo"),
109 (null_string_locale, 56.7),
110 ),
111 False,
112 ),
113 (
114 ns.LOCALE,
115 (
116 (null_string_locale, 6, "aa--", 5, "..", 34, "eE++", 1),
117 ("//ffoOlLdDeErR ((", 1, "))//ffoOoO"),
118 (null_string_locale, 56.7),
119 ),
120 True,
121 ),
122 (
123 ns.LOCALE | ns.CAPITALFIRST,
124 (
125 (("",), (null_string_locale, 6, "A-", 5, ".", 34, "e+", 1)),
126 (("/",), ("/Folder (", 1, ")/Foo")),
127 (("",), (null_string_locale, 56.7)),
128 ),
129 False,
130 ),
131 ],
132 )
133 @pytest.mark.usefixtures("with_locale_en_us")
134 def test_natsort_keygen_with_locale(mocker, arbitrary_input, alg, expected, is_dumb):
135 # First, apply the correct strxfrm function to the string values.
90136 strxfrm = get_strxfrm()
91 with patch('natsort.compat.locale.dumb_sort', return_value=False):
92 assert natsort_keygen(alg=ns.LA | ns.C)(INPUT) == ((('',), (null_string_locale, 6, strxfrm('A-'), 5, strxfrm('.'), 34, strxfrm('e+'), 1)), (('/',), (strxfrm('/Folder ('), 1, strxfrm(')/Foo'))), (('',), (null_string_locale, 56.7)))
93 if PY_VERSION >= 3: assert natsort_keygen(alg=ns.LA | ns.C)(b'6A-5.034e+1') == (b'6A-5.034e+1',)
94 locale.setlocale(locale.LC_ALL, str(''))
137 expected = [list(sub) for sub in expected]
138 try:
139 for i in (2, 4, 6):
140 expected[0][i] = strxfrm(expected[0][i])
141 for i in (0, 2):
142 expected[1][i] = strxfrm(expected[1][i])
143 expected = tuple(tuple(sub) for sub in expected)
144 except IndexError: # ns.LOCALE | ns.CAPITALFIRST
145 expected = [[list(subsub) for subsub in sub] for sub in expected]
146 for i in (2, 4, 6):
147 expected[0][1][i] = strxfrm(expected[0][1][i])
148 for i in (0, 2):
149 expected[1][1][i] = strxfrm(expected[1][1][i])
150 expected = tuple(tuple(tuple(subsub) for subsub in sub) for sub in expected)
151
152 with mocker.patch("natsort.compat.locale.dumb_sort", return_value=is_dumb):
153 ns_key = natsort_keygen(alg=alg)
154 assert ns_key(arbitrary_input) == expected
95155
96156
97 def test_natsort_keygen_splits_input_with_path():
98 assert natsort_keygen(alg=ns.P | ns.G)(INPUT) == ((('', 6, 'aA--', 5, '..', 34, 'ee++', 1),), (('//',), ('fFoollddeerr ((', 1, '))'), ('fFoooo',)), (('', 56.7),))
99 if PY_VERSION >= 3: assert natsort_keygen(alg=ns.P | ns.G)(b'6A-5.034e+1') == ((b'6A-5.034e+1',),)
100
101
102 def test_natsort_keygen_splits_input_with_ignorecase():
103 assert natsort_keygen(alg=ns.IC)(INPUT) == (('', 6, 'a-', 5, '.', 34, 'e+', 1), ('/folder (', 1, ')/foo'), ('', 56.7))
104 if PY_VERSION >= 3: assert natsort_keygen(alg=ns.IC)(b'6A-5.034e+1') == (b'6a-5.034e+1',)
157 @pytest.mark.parametrize(
158 "alg, is_dumb",
159 [(ns.LOCALE, False), (ns.LOCALE, True), (ns.LOCALE | ns.CAPITALFIRST, False)],
160 )
161 @pytest.mark.skipif(PY_VERSION < 3.0, reason="special bytes handling only on Python3")
162 @pytest.mark.usefixtures("with_locale_en_us")
163 def test_natsort_keygen_with_locale_bytes(mocker, bytes_input, alg, is_dumb):
164 expected = (b"6A-5.034e+1",)
165 with mocker.patch("natsort.compat.locale.dumb_sort", return_value=is_dumb):
166 ns_key = natsort_keygen(alg=alg)
167 assert ns_key(bytes_input) == expected
22 Here are a collection of examples of how this module can be used.
33 See the README or the natsort homepage for more details.
44 """
5 from __future__ import unicode_literals, print_function
5 from __future__ import print_function, unicode_literals
6
7 from operator import itemgetter
8
69 import pytest
7 import locale
10 from natsort import as_utf8, natsorted, ns
811 from natsort.compat.py23 import PY_VERSION
9 from operator import itemgetter
1012 from pytest import raises
11 from natsort import (
12 natsorted,
13 ns,
14 )
15 from compat.locale import (
16 load_locale,
17 has_locale_de_DE,
18 )
19
20
21 def test_natsorted_returns_strings_with_numbers_in_ascending_order():
22 a = ['a2', 'a5', 'a9', 'a1', 'a4', 'a10', 'a6']
23 assert natsorted(a) == ['a1', 'a2', 'a4', 'a5', 'a6', 'a9', 'a10']
24
25
26 def test_natsorted_returns_list_of_numbers_sorted_as_signed_floats_with_exponents():
27 a = ['a50', 'a51.', 'a50.31', 'a-50', 'a50.4', 'a5.034e1', 'a50.300']
28 assert natsorted(a, alg=ns.REAL) == ['a-50', 'a50', 'a50.300', 'a50.31', 'a5.034e1', 'a50.4', 'a51.']
29
30
31 def test_natsorted_returns_list_of_numbers_sorted_as_unsigned_floats_without_exponents_with_NOEXP_option():
32 a = ['a50', 'a51.', 'a50.31', 'a-50', 'a50.4', 'a5.034e1', 'a50.300']
33 assert natsorted(a, alg=ns.N | ns.F | ns.U) == ['a5.034e1', 'a50', 'a50.300', 'a50.31', 'a50.4', 'a51.', 'a-50']
13
14
15 @pytest.fixture
16 def float_list():
17 return ["a50", "a51.", "a50.31", "a-50", "a50.4", "a5.034e1", "a50.300"]
18
19
20 @pytest.fixture
21 def fruit_list():
22 return ["Apple", "corn", "Corn", "Banana", "apple", "banana"]
23
24
25 @pytest.fixture
26 def mixed_list():
27 return ["Ä", "0", "ä", 3, "b", 1.5, "2", "Z"]
28
29
30 def test_natsorted_numbers_in_ascending_order():
31 given = ["a2", "a5", "a9", "a1", "a4", "a10", "a6"]
32 expected = ["a1", "a2", "a4", "a5", "a6", "a9", "a10"]
33 assert natsorted(given) == expected
34
35
36 def test_natsorted_can_sort_as_signed_floats_with_exponents(float_list):
37 expected = ["a-50", "a50", "a50.300", "a50.31", "a5.034e1", "a50.4", "a51."]
38 assert natsorted(float_list, alg=ns.REAL) == expected
39
40
41 @pytest.mark.parametrize(
3442 # UNSIGNED is default
35 assert natsorted(a, alg=ns.NOEXP | ns.FLOAT) == ['a5.034e1', 'a50', 'a50.300', 'a50.31', 'a50.4', 'a51.', 'a-50']
36
37
38 def test_natsorted_returns_list_of_numbers_sorted_as_unsigned_ints_with_INT_option():
39 a = ['a50', 'a51.', 'a50.31', 'a-50', 'a50.4', 'a5.034e1', 'a50.300']
40 assert natsorted(a, alg=ns.INT) == ['a5.034e1', 'a50', 'a50.4', 'a50.31', 'a50.300', 'a51.', 'a-50']
41 # INT is default
42 assert natsorted(a) == ['a5.034e1', 'a50', 'a50.4', 'a50.31', 'a50.300', 'a51.', 'a-50']
43
44
45 def test_natsorted_returns_list_of_numbers_sorted_as_unsigned_ints_with_DIGIT_and_VERSION_option():
46 a = ['a50', 'a51.', 'a50.31', 'a-50', 'a50.4', 'a5.034e1', 'a50.300']
47 assert natsorted(a, alg=ns.DIGIT) == ['a5.034e1', 'a50', 'a50.4', 'a50.31', 'a50.300', 'a51.', 'a-50']
48 assert natsorted(a, alg=ns.VERSION) == ['a5.034e1', 'a50', 'a50.4', 'a50.31', 'a50.300', 'a51.', 'a-50']
49
50
51 def test_natsorted_returns_list_of_numbers_sorted_as_signed_ints_with_SIGNED_option():
52 a = ['a50', 'a51.', 'a50.31', 'a-50', 'a50.4', 'a5.034e1', 'a50.300']
53 assert natsorted(a, alg=ns.SIGNED) == ['a-50', 'a5.034e1', 'a50', 'a50.4', 'a50.31', 'a50.300', 'a51.']
54
55
56 def test_natsorted_returns_list_of_numbers_sorted_accounting_for_sign_with_SIGNED_option():
57 a = ['a-5', 'a7', 'a+2']
58 assert natsorted(a, alg=ns.SIGNED) == ['a-5', 'a+2', 'a7']
59
60
61 def test_natsorted_returns_list_of_numbers_sorted_not_accounting_for_sign_without_SIGNED_option():
62 a = ['a-5', 'a7', 'a+2']
63 assert natsorted(a) == ['a7', 'a+2', 'a-5']
64
65
66 def test_natsorted_returns_sorted_list_of_version_numbers_by_default_or_with_VERSION_option():
67 a = ['1.9.9a', '1.11', '1.9.9b', '1.11.4', '1.10.1']
68 assert natsorted(a) == ['1.9.9a', '1.9.9b', '1.10.1', '1.11', '1.11.4']
69 assert natsorted(a, alg=ns.VERSION) == ['1.9.9a', '1.9.9b', '1.10.1', '1.11', '1.11.4']
70
71
72 def test_natsorted_returns_sorted_list_with_mixed_type_input_and_does_not_raise_TypeError_on_Python3():
73 # You can mix types with natsorted. This can get around the new
74 # 'unorderable types' issue with Python 3.
75 a = [6, 4.5, '7', '2.5', 'a']
76 assert natsorted(a) == ['2.5', 4.5, 6, '7', 'a']
77 a = [46, '5a5b2', 'af5', '5a5-4']
78 assert natsorted(a) == ['5a5-4', '5a5b2', 46, 'af5']
79
80
81 def test_natsorted_with_mixed_input_returns_sorted_results_without_error():
82 a = ['0', 'Á', '2', 'Z']
83 assert natsorted(a) == ['0', '2', 'Á', 'Z']
84 assert natsorted(a, alg=ns.NUMAFTER) == ['Á', 'Z', '0', '2']
85 a = ['2', 'ä', 'b', 1.5, 3]
86 assert natsorted(a) == [1.5, '2', 3, 'ä', 'b']
87 assert natsorted(a, alg=ns.NUMAFTER) == ['ä', 'b', 1.5, '2', 3]
88
89
90 def test_natsorted_with_nan_input_returns_sorted_results_with_nan_last_with_NANLAST():
91 a = ['25', 5, float('nan'), 1E40]
43 "alg",
44 [ns.NOEXP | ns.FLOAT | ns.UNSIGNED, ns.NOEXP | ns.FLOAT],
45 )
46 def test_natsorted_can_sort_as_unsigned_and_ignore_exponents(float_list, alg):
47 expected = ["a5.034e1", "a50", "a50.300", "a50.31", "a50.4", "a51.", "a-50"]
48 assert natsorted(float_list, alg=alg) == expected
49
50
51 # INT, DIGIT, and VERSION are all equivalent.
52 @pytest.mark.parametrize("alg", [ns.DEFAULT, ns.INT, ns.DIGIT, ns.VERSION])
53 def test_natsorted_can_sort_as_unsigned_ints_which_is_default(float_list, alg):
54 expected = ["a5.034e1", "a50", "a50.4", "a50.31", "a50.300", "a51.", "a-50"]
55 assert natsorted(float_list, alg=alg) == expected
56
57
58 def test_natsorted_can_sort_as_signed_ints(float_list):
59 expected = ["a-50", "a5.034e1", "a50", "a50.4", "a50.31", "a50.300", "a51."]
60 assert natsorted(float_list, alg=ns.SIGNED) == expected
61
62
63 @pytest.mark.parametrize(
64 "alg, expected",
65 [(ns.UNSIGNED, ["a7", "a+2", "a-5"]), (ns.SIGNED, ["a-5", "a+2", "a7"])],
66 )
67 def test_natsorted_can_sort_with_or_without_accounting_for_sign(alg, expected):
68 given = ["a-5", "a7", "a+2"]
69 assert natsorted(given, alg=alg) == expected
70
71
72 @pytest.mark.parametrize("alg", [ns.DEFAULT, ns.VERSION])
73 def test_natsorted_can_sort_as_version_numbers(alg):
74 given = ["1.9.9a", "1.11", "1.9.9b", "1.11.4", "1.10.1"]
75 expected = ["1.9.9a", "1.9.9b", "1.10.1", "1.11", "1.11.4"]
76 assert natsorted(given, alg=alg) == expected
77
78
79 @pytest.mark.parametrize(
80 "alg, expected",
81 [
82 (ns.DEFAULT, ["0", 1.5, "2", 3, "Ä", "Z", "ä", "b"]),
83 (ns.NUMAFTER, ["Ä", "Z", "ä", "b", "0", 1.5, "2", 3]),
84 ],
85 )
86 def test_natsorted_handles_mixed_types(mixed_list, alg, expected):
87 assert natsorted(mixed_list, alg=alg) == expected
88
89
90 @pytest.mark.parametrize(
91 "alg, expected, slc",
92 [
93 (ns.DEFAULT, [float("nan"), 5, "25", 1E40], slice(1, None)),
94 (ns.NANLAST, [5, "25", 1E40, float("nan")], slice(None, 3)),
95 ],
96 )
97 def test_natsorted_handles_nan(alg, expected, slc):
98 given = ["25", 5, float("nan"), 1E40]
9299 # The slice is because NaN != NaN
93 assert natsorted(a, alg=ns.NANLAST)[:3] == [5, '25', 1E40, float('nan')][:3]
94
95
96 def test_natsorted_with_nan_input_returns_sorted_results_with_nan_first_without_NANLAST():
97 a = ['25', 5, float('nan'), 1E40]
98 # The slice is because NaN != NaN
99 assert natsorted(a)[1:] == [float('nan'), 5, '25', 1E40][1:]
100
101
102 def test_natsorted_with_mixed_input_raises_TypeError_if_bytes_type_is_involved_on_Python3():
103 if PY_VERSION >= 3:
104 with raises(TypeError) as e:
105 assert natsorted(['ä', b'b'])
106 assert 'bytes' in str(e.value)
107 else:
108 assert True
109
110
111 def test_natsorted_raises_ValueError_for_non_iterable_input():
112 with raises(TypeError) as err:
100 # noinspection PyUnresolvedReferences
101 assert natsorted(given, alg=alg)[slc] == expected[slc]
102
103
104 @pytest.mark.skipif(PY_VERSION < 3.0, reason="error is only raised on Python 3")
105 def test_natsorted_with_mixed_bytes_and_str_input_raises_type_error():
106 with raises(TypeError, match="bytes"):
107 natsorted(["ä", b"b"])
108
109 # ...unless you use as_utf (or some other decoder).
110 assert natsorted(["ä", b"b"], key=as_utf8) == ["ä", b"b"]
111
112
113 def test_natsorted_raises_type_error_for_non_iterable_input():
114 with raises(TypeError, match="'int' object is not iterable"):
113115 natsorted(100)
114 assert str(err.value) == "'int' object is not iterable"
115
116
117 def test_natsorted_recursivley_applies_key_to_nested_lists_to_return_sorted_nested_list():
118 data = [['a1', 'a5'], ['a1', 'a40'], ['a10', 'a1'], ['a2', 'a5']]
119 assert natsorted(data) == [['a1', 'a5'], ['a1', 'a40'], ['a2', 'a5'], ['a10', 'a1']]
116
117
118 def test_natsorted_recurses_into_nested_lists():
119 given = [["a1", "a5"], ["a1", "a40"], ["a10", "a1"], ["a2", "a5"]]
120 expected = [["a1", "a5"], ["a1", "a40"], ["a2", "a5"], ["a10", "a1"]]
121 assert natsorted(given) == expected
120122
121123
122124 def test_natsorted_applies_key_to_each_list_element_before_sorting_list():
123 b = [('a', 'num3'), ('b', 'num5'), ('c', 'num2')]
124 assert natsorted(b, key=itemgetter(1)) == [('c', 'num2'), ('a', 'num3'), ('b', 'num5')]
125
126
127 def test_natsorted_returns_list_in_reversed_order_with_reverse_option():
128 a = ['a50', 'a51.', 'a50.31', 'a50.4', 'a5.034e1', 'a50.300']
129 assert natsorted(a, reverse=True) == natsorted(a)[::-1]
130
131
132 def test_natsorted_sorts_OS_generated_paths_incorrectly_without_PATH_option():
133 a = ['/p/Folder (10)/file.tar.gz',
134 '/p/Folder/file.tar.gz',
135 '/p/Folder (1)/file (1).tar.gz',
136 '/p/Folder (1)/file.tar.gz']
137 assert natsorted(a) == ['/p/Folder (1)/file (1).tar.gz',
138 '/p/Folder (1)/file.tar.gz',
139 '/p/Folder (10)/file.tar.gz',
140 '/p/Folder/file.tar.gz']
141
142
143 def test_natsorted_sorts_OS_generated_paths_correctly_with_PATH_option():
144 a = ['/p/Folder (10)/file.tar.gz',
145 '/p/Folder/file.tar.gz',
146 '/p/Folder (1)/file (1).tar.gz',
147 '/p/Folder (1)/file.tar.gz']
148 assert natsorted(a, alg=ns.PATH) == ['/p/Folder/file.tar.gz',
149 '/p/Folder (1)/file.tar.gz',
150 '/p/Folder (1)/file (1).tar.gz',
151 '/p/Folder (10)/file.tar.gz']
152
153
154 def test_natsorted_can_handle_sorting_paths_and_numbers_with_PATH():
125 given = [("a", "num3"), ("b", "num5"), ("c", "num2")]
126 expected = [("c", "num2"), ("a", "num3"), ("b", "num5")]
127 assert natsorted(given, key=itemgetter(1)) == expected
128
129
130 def test_natsorted_returns_list_in_reversed_order_with_reverse_option(float_list):
131 expected = natsorted(float_list)[::-1]
132 assert natsorted(float_list, reverse=True) == expected
133
134
135 def test_natsorted_handles_filesystem_paths():
136 given = [
137 "/p/Folder (10)/file.tar.gz",
138 "/p/Folder/file.tar.gz",
139 "/p/Folder (1)/file (1).tar.gz",
140 "/p/Folder (1)/file.tar.gz",
141 ]
142 expected_correct = [
143 "/p/Folder/file.tar.gz",
144 "/p/Folder (1)/file.tar.gz",
145 "/p/Folder (1)/file (1).tar.gz",
146 "/p/Folder (10)/file.tar.gz",
147 ]
148 expected_incorrect = [
149 "/p/Folder (1)/file (1).tar.gz",
150 "/p/Folder (1)/file.tar.gz",
151 "/p/Folder (10)/file.tar.gz",
152 "/p/Folder/file.tar.gz",
153 ]
154 # Is incorrect by default.
155 assert natsorted(given) == expected_incorrect
156 # Need ns.PATH to make it correct.
157 assert natsorted(given, alg=ns.PATH) == expected_correct
158
159
160 def test_natsorted_handles_numbers_and_filesystem_paths_simultaneously():
155161 # You can sort paths and numbers, not that you'd want to
156 a = ['/Folder (9)/file.exe', 43]
157 assert natsorted(a, alg=ns.PATH) == [43, '/Folder (9)/file.exe']
158
159
160 def test_natsorted_returns_results_in_ASCII_order_with_no_case_options():
161 a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana']
162 assert natsorted(a) == ['Apple', 'Banana', 'Corn', 'apple', 'banana', 'corn']
163
164
165 def test_natsorted_returns_results_sorted_by_lowercase_ASCII_order_with_IGNORECASE():
166 a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana']
167 assert natsorted(a, alg=ns.IGNORECASE) == ['Apple', 'apple', 'Banana', 'banana', 'corn', 'Corn']
168
169
170 def test_natsorted_returns_results_in_ASCII_order_but_with_lowercase_letters_first_with_LOWERCASEFIRST():
171 a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana']
172 assert natsorted(a, alg=ns.LOWERCASEFIRST) == ['apple', 'banana', 'corn', 'Apple', 'Banana', 'Corn']
173
174
175 def test_natsorted_returns_results_with_uppercase_and_lowercase_letters_grouped_together_with_GROUPLETTERS():
176 a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana']
177 assert natsorted(a, alg=ns.GROUPLETTERS) == ['Apple', 'apple', 'Banana', 'banana', 'Corn', 'corn']
178
179
180 def test_natsorted_returns_results_in_natural_order_with_GROUPLETTERS_and_LOWERCASEFIRST():
181 a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana']
182 assert natsorted(a, alg=ns.G | ns.LF) == ['apple', 'Apple', 'banana', 'Banana', 'corn', 'Corn']
183
184
185 def test_natsorted_places_uppercase_letters_before_lowercase_letters_for_nested_input():
186 b = [('A5', 'a6'), ('a3', 'a1')]
187 assert natsorted(b) == [('A5', 'a6'), ('a3', 'a1')]
188
189
190 def test_natsorted_with_LOWERCASEFIRST_places_lowercase_letters_before_uppercase_letters_for_nested_input():
191 b = [('A5', 'a6'), ('a3', 'a1')]
192 assert natsorted(b, alg=ns.LOWERCASEFIRST) == [('a3', 'a1'), ('A5', 'a6')]
193
194
195 def test_natsorted_with_IGNORECASE_sorts_without_regard_to_case_for_nested_input():
196 b = [('A5', 'a6'), ('a3', 'a1')]
197 assert natsorted(b, alg=ns.IGNORECASE) == [('a3', 'a1'), ('A5', 'a6')]
198
199
200 def test_natsorted_with_LOCALE_returns_results_sorted_by_lowercase_first_and_grouped_letters():
201 a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana']
202 load_locale('en_US')
203 assert natsorted(a, alg=ns.LOCALE) == ['apple', 'Apple', 'banana', 'Banana', 'corn', 'Corn']
204 locale.setlocale(locale.LC_ALL, str(''))
205
206
207 def test_natsorted_with_LOCALE_and_CAPITALFIRST_returns_results_sorted_by_capital_first_and_ungrouped():
208 a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana']
209 load_locale('en_US')
210 assert natsorted(a, alg=ns.LOCALE | ns.CAPITALFIRST) == ['Apple', 'Banana', 'Corn', 'apple', 'banana', 'corn']
211 locale.setlocale(locale.LC_ALL, str(''))
212
213
214 def test_natsorted_with_LOCALE_and_LOWERCASEFIRST_returns_results_sorted_by_uppercase_first_and_grouped_letters():
215 a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana']
216 load_locale('en_US')
217 assert natsorted(a, alg=ns.LOCALE | ns.LOWERCASEFIRST) == ['Apple', 'apple', 'Banana', 'banana', 'Corn', 'corn']
218 locale.setlocale(locale.LC_ALL, str(''))
219
220
221 def test_natsorted_with_LOCALE_and_CAPITALFIRST_and_LOWERCASE_returns_results_sorted_by_capital_last_and_ungrouped():
222 a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana']
223 load_locale('en_US')
224 assert natsorted(a, alg=ns.LOCALE | ns.CAPITALFIRST | ns.LOWERCASEFIRST) == ['apple', 'banana', 'corn', 'Apple', 'Banana', 'Corn']
225 locale.setlocale(locale.LC_ALL, str(''))
226
227
228 def test_natsorted_with_LOCALE_and_en_setting_returns_results_sorted_by_en_language():
229 load_locale('en_US')
230 a = ['c', 'a5,467.86', 'ä', 'b', 'a5367.86', 'a5,6', 'a5,50']
231 assert natsorted(a, alg=ns.LOCALE | ns.F) == ['a5,6', 'a5,50', 'a5367.86', 'a5,467.86', 'ä', 'b', 'c']
232 locale.setlocale(locale.LC_ALL, str(''))
233
234
235 @pytest.mark.skipif(not has_locale_de_DE, reason='requires de_DE locale and working locale')
236 def test_natsorted_with_LOCALE_and_de_setting_returns_results_sorted_by_de_language():
237 load_locale('de_DE')
238 a = ['c', 'a5.467,86', 'ä', 'b', 'a5367.86', 'a5,6', 'a5,50']
239 assert natsorted(a, alg=ns.LOCALE | ns.F) == ['a5,50', 'a5,6', 'a5367.86', 'a5.467,86', 'ä', 'b', 'c']
240 locale.setlocale(locale.LC_ALL, str(''))
241
242
243 def test_natsorted_with_LOCALE_and_mixed_input_returns_sorted_results_without_error():
244 load_locale('en_US')
245 a = ['0', 'Á', '2', 'Z']
246 assert natsorted(a, alg=ns.LOCALE) == ['0', '2', 'Á', 'Z']
247 assert natsorted(a, alg=ns.LOCALE | ns.NUMAFTER) == ['Á', 'Z', '0', '2']
248 a = ['2', 'ä', 'b', 1.5, 3]
249 assert natsorted(a, alg=ns.LOCALE) == [1.5, '2', 3, 'ä', 'b']
250 assert natsorted(a, alg=ns.LOCALE | ns.NUMAFTER) == ['ä', 'b', 1.5, '2', 3]
251 locale.setlocale(locale.LC_ALL, str(''))
252
253
254 def test_natsorted_with_LOCALE_and_UNGROUPLETTERS_and_mixed_input_returns_sorted_results_without_error():
255 load_locale('en_US')
256 a = ['0', 'Á', '2', 'Z']
257 assert natsorted(a, alg=ns.LOCALE | ns.UNGROUPLETTERS) == ['0', '2', 'Á', 'Z']
258 assert natsorted(a, alg=ns.LOCALE | ns.UNGROUPLETTERS | ns.NUMAFTER) == ['Á', 'Z', '0', '2']
259 a = ['2', 'ä', 'b', 1.5, 3]
260 assert natsorted(a, alg=ns.LOCALE | ns.UNGROUPLETTERS) == [1.5, '2', 3, 'ä', 'b']
261 assert natsorted(a, alg=ns.LOCALE | ns.UNGROUPLETTERS | ns.NUMAFTER) == ['ä', 'b', 1.5, '2', 3]
262 locale.setlocale(locale.LC_ALL, str(''))
263
264
265 def test_natsorted_with_PATH_and_LOCALE_and_UNGROUPLETTERS_and_mixed_input_returns_sorted_results_without_error():
266 load_locale('en_US')
267 a = ['0', 'Á', '2', 'Z']
268 assert natsorted(a, alg=ns.PATH | ns.LOCALE | ns.UNGROUPLETTERS) == ['0', '2', 'Á', 'Z']
269 assert natsorted(a, alg=ns.PATH | ns.LOCALE | ns.UNGROUPLETTERS | ns.NUMAFTER) == ['Á', 'Z', '0', '2']
270 a = ['2', 'ä', 'b', 1.5, 3]
271 assert natsorted(a, alg=ns.PATH | ns.LOCALE | ns.UNGROUPLETTERS) == [1.5, '2', 3, 'ä', 'b']
272 assert natsorted(a, alg=ns.PATH | ns.LOCALE | ns.UNGROUPLETTERS | ns.NUMAFTER) == ['ä', 'b', 1.5, '2', 3]
273 locale.setlocale(locale.LC_ALL, str(''))
274
275
276 def test_natsorted_sorts_an_odd_collection_of_string():
277 a = ['Corn', 'apple', 'Banana', '73', 'Apple', '5039', 'corn', '~~~~~~', 'banana']
278 assert natsorted(a) == ['73', '5039', 'Apple', 'Banana', 'Corn',
279 'apple', 'banana', 'corn', '~~~~~~']
280 assert natsorted(a, alg=ns.NUMAFTER) == ['Apple', 'Banana', 'Corn',
281 'apple', 'banana', 'corn', '~~~~~~', '73', '5039']
162 given = ["/Folder (9)/file.exe", 43]
163 expected = [43, "/Folder (9)/file.exe"]
164 assert natsorted(given, alg=ns.PATH) == expected
165
166
167 @pytest.mark.parametrize(
168 "alg, expected",
169 [
170 (ns.DEFAULT, ["Apple", "Banana", "Corn", "apple", "banana", "corn"]),
171 (ns.IGNORECASE, ["Apple", "apple", "Banana", "banana", "corn", "Corn"]),
172 (ns.LOWERCASEFIRST, ["apple", "banana", "corn", "Apple", "Banana", "Corn"]),
173 (ns.GROUPLETTERS, ["Apple", "apple", "Banana", "banana", "Corn", "corn"]),
174 (ns.G | ns.LF, ["apple", "Apple", "banana", "Banana", "corn", "Corn"]),
175 ],
176 )
177 def test_natsorted_supports_case_handling(alg, expected, fruit_list):
178 assert natsorted(fruit_list, alg=alg) == expected
179
180
181 @pytest.mark.parametrize(
182 "alg, expected",
183 [
184 (ns.DEFAULT, [("A5", "a6"), ("a3", "a1")]),
185 (ns.LOWERCASEFIRST, [("a3", "a1"), ("A5", "a6")]),
186 (ns.IGNORECASE, [("a3", "a1"), ("A5", "a6")]),
187 ],
188 )
189 def test_natsorted_supports_nested_case_handling(alg, expected):
190 given = [("A5", "a6"), ("a3", "a1")]
191 assert natsorted(given, alg=alg) == expected
192
193
194 @pytest.mark.parametrize(
195 "alg, expected",
196 [
197 (ns.DEFAULT, ["apple", "Apple", "banana", "Banana", "corn", "Corn"]),
198 (ns.CAPITALFIRST, ["Apple", "Banana", "Corn", "apple", "banana", "corn"]),
199 (ns.LOWERCASEFIRST, ["Apple", "apple", "Banana", "banana", "Corn", "corn"]),
200 (ns.C | ns.LF, ["apple", "banana", "corn", "Apple", "Banana", "Corn"]),
201 ],
202 )
203 @pytest.mark.usefixtures("with_locale_en_us")
204 def test_natsorted_can_sort_using_locale(fruit_list, alg, expected):
205 assert natsorted(fruit_list, alg=ns.LOCALE | alg) == expected
206
207
208 @pytest.mark.usefixtures("with_locale_en_us")
209 def test_natsorted_can_sort_locale_specific_numbers_en():
210 given = ["c", "a5,467.86", "ä", "b", "a5367.86", "a5,6", "a5,50"]
211 expected = ["a5,6", "a5,50", "a5367.86", "a5,467.86", "ä", "b", "c"]
212 assert natsorted(given, alg=ns.LOCALE | ns.F) == expected
213
214
215 @pytest.mark.usefixtures("with_locale_de_de")
216 def test_natsorted_can_sort_locale_specific_numbers_de():
217 given = ["c", "a5.467,86", "ä", "b", "a5367.86", "a5,6", "a5,50"]
218 expected = ["a5,50", "a5,6", "a5367.86", "a5.467,86", "ä", "b", "c"]
219 assert natsorted(given, alg=ns.LOCALE | ns.F) == expected
220
221
222 @pytest.mark.parametrize(
223 "alg, expected",
224 [
225 (ns.DEFAULT, ["0", 1.5, "2", 3, "ä", "Ä", "b", "Z"]),
226 (ns.NUMAFTER, ["ä", "Ä", "b", "Z", "0", 1.5, "2", 3]),
227 (ns.UNGROUPLETTERS, ["0", 1.5, "2", 3, "Ä", "Z", "ä", "b"]),
228 (ns.UG | ns.NA, ["Ä", "Z", "ä", "b", "0", 1.5, "2", 3]),
229 # Adding PATH changes nothing.
230 (ns.PATH, ["0", 1.5, "2", 3, "ä", "Ä", "b", "Z"]),
231 (ns.PATH | ns.NUMAFTER, ["ä", "Ä", "b", "Z", "0", 1.5, "2", 3]),
232 (ns.PATH | ns.UNGROUPLETTERS, ["0", 1.5, "2", 3, "Ä", "Z", "ä", "b"]),
233 (ns.PATH | ns.UG | ns.NA, ["Ä", "Z", "ä", "b", "0", 1.5, "2", 3]),
234 ],
235 )
236 @pytest.mark.usefixtures("with_locale_en_us")
237 def test_natsorted_handles_mixed_types_with_locale(mixed_list, alg, expected):
238 assert natsorted(mixed_list, alg=ns.LOCALE | alg) == expected
239
240
241 @pytest.mark.parametrize(
242 "alg, expected",
243 [
244 (ns.DEFAULT, ["73", "5039", "Banana", "apple", "corn", "~~~~~~"]),
245 (ns.NUMAFTER, ["Banana", "apple", "corn", "~~~~~~", "73", "5039"]),
246 ],
247 )
248 def test_natsorted_sorts_an_odd_collection_of_strings(alg, expected):
249 given = ["apple", "Banana", "73", "5039", "corn", "~~~~~~"]
250 assert natsorted(given, alg=alg) == expected
282251
283252
284253 def test_natsorted_sorts_mixed_ascii_and_non_ascii_numbers():
285 a = ['1st street', '10th street', '2nd street', '2 street', '1 street', '1street',
286 '11 street', 'street 2', 'street 1', 'Street 11', '۲ street', '۱ street', '۱street',
287 '۱۲street', '۱۱ street', 'street ۲', 'street ۱', 'street ۱', 'street ۱۲', 'street ۱۱']
288 expected = ['1 street', '۱ street', '1st street', '1street', '۱street', '2 street', '۲ street',
289 '2nd street', '10th street', '11 street', '۱۱ street', '۱۲street', 'street 1',
290 'street ۱', 'street ۱', 'street 2', 'street ۲', 'Street 11', 'street ۱۱', 'street ۱۲']
291 assert natsorted(a, alg=ns.IGNORECASE) == expected
254 given = [
255 "1st street",
256 "10th street",
257 "2nd street",
258 "2 street",
259 "1 street",
260 "1street",
261 "11 street",
262 "street 2",
263 "street 1",
264 "Street 11",
265 "۲ street",
266 "۱ street",
267 "۱street",
268 "۱۲street",
269 "۱۱ street",
270 "street ۲",
271 "street ۱",
272 "street ۱",
273 "street ۱۲",
274 "street ۱۱",
275 ]
276 expected = [
277 "1 street",
278 "۱ street",
279 "1st street",
280 "1street",
281 "۱street",
282 "2 street",
283 "۲ street",
284 "2nd street",
285 "10th street",
286 "11 street",
287 "۱۱ street",
288 "۱۲street",
289 "street 1",
290 "street ۱",
291 "street ۱",
292 "street 2",
293 "street ۲",
294 "Street 11",
295 "street ۱۱",
296 "street ۱۲",
297 ]
298 assert natsorted(given, alg=ns.IGNORECASE) == expected
22 Here are a collection of examples of how this module can be used.
33 See the README or the natsort homepage for more details.
44 """
5 from __future__ import unicode_literals, print_function
5 from __future__ import print_function, unicode_literals
6
67 from operator import itemgetter
7 from natsort.compat.py23 import PY_VERSION
8
9 import pytest
810 from natsort import (
9 natsorted,
10 index_natsorted,
11 versorted,
12 index_versorted,
11 as_ascii,
12 as_utf8,
13 decoder,
1314 humansorted,
1415 index_humansorted,
16 index_natsorted,
17 index_realsorted,
18 index_versorted,
19 natsorted,
20 ns,
21 order_by_index,
1522 realsorted,
16 index_realsorted,
17 order_by_index,
18 ns,
19 decoder,
20 as_ascii,
21 as_utf8,
23 versorted,
2224 )
25 from natsort.compat.py23 import PY_VERSION
26
27
28 @pytest.fixture
29 def version_list():
30 return ["1.9.9a", "1.11", "1.9.9b", "1.11.4", "1.10.1"]
31
32
33 @pytest.fixture
34 def float_list():
35 return ["a50", "a51.", "a50.31", "a-50", "a50.4", "a5.034e1", "a50.300"]
36
37
38 @pytest.fixture
39 def fruit_list():
40 return ["Apple", "corn", "Corn", "Banana", "apple", "banana"]
2341
2442
2543 def test_decoder_returns_function_that_can_decode_bytes_but_return_non_bytes_as_is():
26 f = decoder('latin1')
27 a = 'bytes'
28 b = 14
29 assert f(b'bytes') == a
30 assert f(b) is b # returns as-is, same object ID
44 func = decoder("latin1")
45 str_obj = "bytes"
46 int_obj = 14
47 assert func(b"bytes") == str_obj
48 assert func(int_obj) is int_obj # returns as-is, same object ID
3149 if PY_VERSION >= 3:
32 assert f(a) is a # same object returned on Python3 b/c only bytes has decode
50 assert (
51 func(str_obj) is str_obj
52 ) # same object returned on Python3 b/c only bytes has decode
3353 else:
34 assert f(a) is not a
35 assert f(a) == a # not same object on Python2 because str can decode
54 assert func(str_obj) is not str_obj
55 assert (
56 func(str_obj) == str_obj
57 ) # not same object on Python2 because str can decode
3658
3759
38 def test_as_ascii_returns_bytes_as_ascii():
39 assert decoder('ascii')(b'bytes') == as_ascii(b'bytes')
60 def test_as_ascii_converts_bytes_to_ascii():
61 assert decoder("ascii")(b"bytes") == as_ascii(b"bytes")
4062
4163
42 def test_as_utf8_returns_bytes_as_utf8():
43 assert decoder('utf8')(b'bytes') == as_utf8(b'bytes')
64 def test_as_utf8_converts_bytes_to_utf8():
65 assert decoder("utf8")(b"bytes") == as_utf8(b"bytes")
4466
4567
46 def test_versorted_returns_results_identical_to_natsorted():
47 a = ['1.9.9a', '1.11', '1.9.9b', '1.11.4', '1.10.1']
68 def test_versorted_is_identical_to_natsorted(version_list):
4869 # versorted is retained for backwards compatibility
49 assert versorted(a) == natsorted(a)
70 assert versorted(version_list) == natsorted(version_list)
5071
5172
52 def test_realsorted_returns_results_identical_to_natsorted_with_REAL():
53 a = ['a50', 'a51.', 'a50.31', 'a-50', 'a50.4', 'a5.034e1', 'a50.300']
54 assert realsorted(a) == natsorted(a, alg=ns.REAL)
73 def test_realsorted_is_identical_to_natsorted_with_real_alg(float_list):
74 assert realsorted(float_list) == natsorted(float_list, alg=ns.REAL)
5575
5676
57 def test_humansorted_returns_results_identical_to_natsorted_with_LOCALE():
58 a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana']
59 assert humansorted(a) == natsorted(a, alg=ns.LOCALE)
77 @pytest.mark.usefixtures("with_locale_en_us")
78 def test_humansorted_is_identical_to_natsorted_with_locale_alg(fruit_list):
79 assert humansorted(fruit_list) == natsorted(fruit_list, alg=ns.LOCALE)
6080
6181
6282 def test_index_natsorted_returns_integer_list_of_sort_order_for_input_list():
63 a = ['num3', 'num5', 'num2']
64 b = ['foo', 'bar', 'baz']
65 index = index_natsorted(a)
83 given = ["num3", "num5", "num2"]
84 other = ["foo", "bar", "baz"]
85 index = index_natsorted(given)
6686 assert index == [2, 0, 1]
67 assert [a[i] for i in index] == ['num2', 'num3', 'num5']
68 assert [b[i] for i in index] == ['baz', 'foo', 'bar']
87 assert [given[i] for i in index] == ["num2", "num3", "num5"]
88 assert [other[i] for i in index] == ["baz", "foo", "bar"]
6989
7090
71 def test_index_natsorted_returns_reversed_integer_list_of_sort_order_for_input_list_with_reverse_option():
72 a = ['num3', 'num5', 'num2']
73 assert index_natsorted(a, reverse=True) == [1, 0, 2]
91 def test_index_natsorted_reverse():
92 given = ["num3", "num5", "num2"]
93 assert index_natsorted(given, reverse=True) == index_natsorted(given)[::-1]
7494
7595
7696 def test_index_natsorted_applies_key_function_before_sorting():
77 c = [('a', 'num3'), ('b', 'num5'), ('c', 'num2')]
78 assert index_natsorted(c, key=itemgetter(1)) == [2, 0, 1]
97 given = [("a", "num3"), ("b", "num5"), ("c", "num2")]
98 expected = [2, 0, 1]
99 assert index_natsorted(given, key=itemgetter(1)) == expected
79100
80101
81 def test_index_natsorted_handles_unorderable_types_error_on_Python3():
82 a = [46, '5a5b2', 'af5', '5a5-4']
83 assert index_natsorted(a) == [3, 1, 0, 2]
102 def test_index_versorted_is_identical_to_index_natsorted(version_list):
103 # index_versorted is retained for backwards compatibility
104 assert index_versorted(version_list) == index_natsorted(version_list)
84105
85106
86 def test_index_natsorted_returns_integer_list_of_nested_input_list():
87 data = [['a1', 'a5'], ['a1', 'a40'], ['a10', 'a1'], ['a2', 'a5']]
88 assert index_natsorted(data) == [0, 1, 3, 2]
107 def test_index_realsorted_is_identical_to_index_natsorted_with_real_alg(float_list):
108 assert index_realsorted(float_list) == index_natsorted(float_list, alg=ns.REAL)
89109
90110
91 def test_index_natsorted_returns_integer_list_in_proper_order_for_input_paths_with_PATH():
92 a = ['/p/Folder (10)/',
93 '/p/Folder/',
94 '/p/Folder (1)/']
95 assert index_natsorted(a, alg=ns.PATH) == [1, 2, 0]
96
97
98 def test_index_versorted_returns_results_identical_to_index_natsorted():
99 a = ['1.9.9a', '1.11', '1.9.9b', '1.11.4', '1.10.1']
100 # index_versorted is retained for backwards compatibility
101 assert index_versorted(a) == index_natsorted(a)
102
103
104 def test_index_realsorted_returns_results_identical_to_index_natsorted_with_REAL():
105 a = ['a50', 'a51.', 'a50.31', 'a-50', 'a50.4', 'a5.034e1', 'a50.300']
106 assert index_realsorted(a) == index_natsorted(a, alg=ns.REAL)
107
108
109 def test_index_humansorted_returns_results_identical_to_index_natsorted_with_LOCALE():
110 a = ['Apple', 'corn', 'Corn', 'Banana', 'apple', 'banana']
111 assert index_humansorted(a) == index_natsorted(a, alg=ns.LOCALE)
111 @pytest.mark.usefixtures("with_locale_en_us")
112 def test_index_humansorted_is_identical_to_index_natsorted_with_locale_alg(fruit_list):
113 assert index_humansorted(fruit_list) == index_natsorted(fruit_list, alg=ns.LOCALE)
112114
113115
114116 def test_order_by_index_sorts_list_according_to_order_of_integer_list():
115 a = ['num3', 'num5', 'num2']
117 given = ["num3", "num5", "num2"]
116118 index = [2, 0, 1]
117 assert order_by_index(a, index) == ['num2', 'num3', 'num5']
118 assert order_by_index(a, index) == [a[i] for i in index]
119 expected = [given[i] for i in index]
120 assert expected == ["num2", "num3", "num5"]
121 assert order_by_index(given, index) == expected
119122
120123
121 def test_order_by_index_returns_generator_with_iter_True():
122 a = ['num3', 'num5', 'num2']
124 def test_order_by_index_returns_generator_with_iter_true():
125 given = ["num3", "num5", "num2"]
123126 index = [2, 0, 1]
124 assert order_by_index(a, index, True) != [a[i] for i in index]
125 assert list(order_by_index(a, index, True)) == [a[i] for i in index]
127 assert order_by_index(given, index, True) != [given[i] for i in index]
128 assert list(order_by_index(given, index, True)) == [given[i] for i in index]
11 """These test the utils.py functions."""
22 from __future__ import unicode_literals
33
4 from natsort.ns_enum import ns
5 from natsort.utils import _parse_bytes_factory
4 import pytest
65 from hypothesis import given
76 from hypothesis.strategies import binary
7 from natsort.ns_enum import ns
8 from natsort.utils import parse_bytes_factory
89
910
10 # Each test has an "example" version for demonstrative purposes,
11 # and a test that uses the hypothesis module.
12
13
14 def test_parse_bytes_factory_makes_function_that_returns_tuple_example():
15 assert _parse_bytes_factory(0)(b'hello') == (b'hello',)
16
17
18 @given(binary())
19 def test_parse_bytes_factory_makes_function_that_returns_tuple(x):
20 assert _parse_bytes_factory(0)(x) == (x,)
21
22
23 def test_parse_bytes_factory_with_IGNORECASE_makes_function_that_returns_tuple_with_lowercase_example():
24 assert _parse_bytes_factory(ns.IGNORECASE)(b'HelLo') == (b'hello',)
25
26
27 @given(binary())
28 def test_parse_bytes_factory_with_IGNORECASE_makes_function_that_returns_tuple_with_lowercase(x):
29 assert _parse_bytes_factory(ns.IGNORECASE)(x) == (x.lower(),)
30
31
32 def test_parse_bytes_factory_with_PATH_makes_function_that_returns_nested_tuple_example():
33 assert _parse_bytes_factory(ns.PATH)(b'hello') == ((b'hello',),)
34
35
36 @given(binary())
37 def test_parse_bytes_factory_with_PATH_makes_function_that_returns_nested_tuple(x):
38 assert _parse_bytes_factory(ns.PATH)(x) == ((x,),)
39
40
41 def test_parse_bytes_factory_with_PATH_and_IGNORECASE_makes_function_that_returns_nested_tuple_with_lowercase_example():
42 assert _parse_bytes_factory(ns.PATH | ns.IGNORECASE)(b'HelLo') == ((b'hello',),)
43
44
45 @given(binary())
46 def test_parse_bytes_factory_with_PATH_and_IGNORECASE_makes_function_that_returns_nested_tuple_with_lowercase(x):
47 assert _parse_bytes_factory(ns.PATH | ns.IGNORECASE)(x) == ((x.lower(),),)
11 @pytest.mark.parametrize(
12 "alg, example_func",
13 [
14 (ns.DEFAULT, lambda x: (x,)),
15 (ns.IGNORECASE, lambda x: (x.lower(),)),
16 # With PATH, it becomes a tested tuple.
17 (ns.PATH, lambda x: ((x,),)),
18 (ns.PATH | ns.IGNORECASE, lambda x: ((x.lower(),),)),
19 ],
20 )
21 @given(x=binary())
22 def test_parse_bytest_factory_makes_function_that_returns_tuple(x, alg, example_func):
23 parse_bytes_func = parse_bytes_factory(alg)
24 assert parse_bytes_func(x) == example_func(x)
11 """These test the utils.py functions."""
22 from __future__ import unicode_literals
33
4 import pytest
5 from hypothesis import given
6 from hypothesis.strategies import floats, integers
47 from natsort.ns_enum import ns
5 from natsort.utils import _parse_number_factory
6 from hypothesis import (
7 given,
8 )
9 from hypothesis.strategies import (
10 floats,
11 integers,
12 )
8 from natsort.utils import parse_number_factory
139
1410
15 # Each test has an "example" version for demonstrative purposes,
16 # and a test that uses the hypothesis module.
11 @pytest.mark.usefixtures("with_locale_en_us")
12 @pytest.mark.parametrize(
13 "alg, example_func",
14 [
15 (ns.DEFAULT, lambda x: ("", x)),
16 (ns.PATH, lambda x: (("", x),)),
17 (ns.UNGROUPLETTERS | ns.LOCALE, lambda x: (("xx",), ("", x))),
18 (ns.PATH | ns.UNGROUPLETTERS | ns.LOCALE, lambda x: ((("xx",), ("", x)),)),
19 ],
20 )
21 @given(x=floats(allow_nan=False) | integers())
22 def test_parse_number_factory_makes_function_that_returns_tuple(x, alg, example_func):
23 parse_number_func = parse_number_factory(alg, "", "xx")
24 assert parse_number_func(x) == example_func(x)
1725
1826
19 def test_parse_number_factory_makes_function_that_returns_tuple_example():
20 assert _parse_number_factory(0, '', '')(57) == ('', 57)
21 assert _parse_number_factory(0, '', '')(float('nan')) == ('', float('-inf'))
22 assert _parse_number_factory(ns.NANLAST, '', '')(float('nan')) == ('', float('+inf'))
23
24
25 @given(floats(allow_nan=False) | integers())
26 def test_parse_number_factory_makes_function_that_returns_tuple(x):
27 assert _parse_number_factory(0, '', '')(x) == ('', x)
28
29
30 def test_parse_number_factory_with_PATH_makes_function_that_returns_nested_tuple_example():
31 assert _parse_number_factory(ns.PATH, '', '')(57) == (('', 57),)
32
33
34 @given(floats(allow_nan=False) | integers())
35 def test_parse_number_factory_with_PATH_makes_function_that_returns_nested_tuple(x):
36 assert _parse_number_factory(ns.PATH, '', '')(x) == (('', x),)
37
38
39 def test_parse_number_factory_with_UNGROUPLETTERS_LOCALE_makes_function_that_returns_nested_tuple_example():
40 assert _parse_number_factory(ns.UNGROUPLETTERS | ns.LOCALE, '', 'xx')(57) == (('xx',), ('', 57))
41
42
43 @given(floats(allow_nan=False) | integers())
44 def test_parse_number_factory_with_UNGROUPLETTERS_LOCALE_makes_function_that_returns_nested_tuple(x):
45 assert _parse_number_factory(ns.UNGROUPLETTERS | ns.LOCALE, '', 'xx')(x) == (('xx',), ('', x))
46
47
48 def test_parse_number_factory_with_PATH_UNGROUPLETTERS_LOCALE_makes_function_that_returns_nested_tuple_example():
49 assert _parse_number_factory(ns.PATH | ns.UNGROUPLETTERS | ns.LOCALE, '', 'xx')(57) == ((('xx',), ('', 57)),)
50
51
52 @given(floats(allow_nan=False) | integers())
53 def test_parse_number_factory_with_PATH_UNGROUPLETTERS_LOCALE_makes_function_that_returns_nested_tuple(x):
54 assert _parse_number_factory(ns.PATH | ns.UNGROUPLETTERS | ns.LOCALE, '', 'xx')(x) == ((('xx',), ('', x)),)
27 @pytest.mark.parametrize(
28 "alg, x, result",
29 [
30 (ns.DEFAULT, 57, ("", 57)),
31 (ns.DEFAULT, float("nan"), ("", float("-inf"))), # NaN transformed to -infinity
32 (ns.NANLAST, float("nan"), ("", float("+inf"))), # NANLAST makes it +infinity
33 ],
34 )
35 def test_parse_number_factory_treats_nan_special(alg, x, result):
36 parse_number_func = parse_number_factory(alg, "", "xx")
37 assert parse_number_func(x) == result
11 """These test the utils.py functions."""
22 from __future__ import unicode_literals
33
4 from pytest import raises
5 from natsort.ns_enum import ns
6 from natsort.utils import (
7 _float_sign_exp_re,
8 _float_nosign_exp_re,
9 _float_sign_noexp_re,
10 _float_nosign_noexp_re,
11 _int_nosign_re,
12 _int_sign_re,
13 _parse_string_factory,
14 _parse_path_factory,
15 )
16 from natsort.compat.py23 import py23_str, PY_VERSION
17 from natsort.compat.fastnumbers import (
18 fast_float,
19 fast_int,
20 )
21 from slow_splitters import (
22 int_splitter,
23 float_splitter,
24 )
25 from hypothesis import (
26 given,
27 example,
28 )
29 from hypothesis.strategies import (
30 lists,
31 text,
32 floats,
33 integers,
34 )
4 import unicodedata
355
36 if PY_VERSION >= 3:
37 long = int
6 import pytest
7 from hypothesis import given
8 from hypothesis.strategies import floats, integers, lists, text
9 from natsort.compat.fastnumbers import fast_float
10 from natsort.compat.py23 import py23_str
11 from natsort.ns_enum import ns, ns_DUMB
12 from natsort.utils import NumericalRegularExpressions as NumRegex
13 from natsort.utils import parse_string_factory
3814
3915
40 def whitespace_check(x):
41 """Simplifies testing"""
42 try:
43 if x.isspace():
44 return x in ' \t\n\r\f\v'
45 else:
46 return True
47 except (AttributeError, TypeError):
48 return True
16 class CustomTuple(tuple):
17 """Used to ensure what is given during testing is what is returned."""
18
19 original = None
4920
5021
51 def no_op(x):
52 """A function that does nothing."""
53 return x
22 def input_transform(x):
23 """Make uppercase."""
24 try:
25 return x.upper()
26 except AttributeError:
27 return x
5428
5529
56 def tuple2(x, dummy):
57 """Make the input a tuple."""
58 return tuple(x)
30 def final_transform(x, original):
31 """Make the input a CustomTuple."""
32 t = CustomTuple(x)
33 t.original = original
34 return t
5935
6036
61 # Each test has an "example" version for demonstrative purposes,
62 # and a test that uses the hypothesis module.
37 @pytest.fixture
38 def parse_string_func(request):
39 """A parse_string_factory result with sample arguments."""
40 sep = ""
41 return parse_string_factory(
42 request.param, # algorirhm
43 sep,
44 NumRegex.int_nosign().split,
45 input_transform,
46 fast_float,
47 final_transform,
48 )
6349
6450
65 def test_parse_string_factory_raises_TypeError_if_given_a_number_example():
66 with raises(TypeError):
67 assert _parse_string_factory(0, '', _float_sign_exp_re.split, no_op, fast_float, tuple2)(50.0)
51 @pytest.mark.parametrize("parse_string_func", [ns.DEFAULT], indirect=True)
52 @given(x=floats() | integers())
53 def test_parse_string_factory_raises_type_error_if_given_number(x, parse_string_func):
54 with pytest.raises(TypeError):
55 assert parse_string_func(x)
6856
6957
70 @given(floats())
71 def test_parse_string_factory_raises_TypeError_if_given_a_number(x):
72 with raises(TypeError):
73 assert _parse_string_factory(0, '', _float_sign_exp_re.split, no_op, fast_float, tuple2)(x)
58 # noinspection PyCallingNonCallable
59 @pytest.mark.parametrize(
60 "parse_string_func, orig_func",
61 [
62 (ns.DEFAULT, lambda x: x.upper()),
63 (ns.LOCALE, lambda x: x.upper()),
64 (ns.LOCALE | ns_DUMB, lambda x: x), # This changes the "original" handling.
65 ],
66 indirect=["parse_string_func"],
67 )
68 @given(
69 x=lists(
70 elements=floats(allow_nan=False) | text() | integers(), min_size=1, max_size=10
71 )
72 )
73 @pytest.mark.usefixtures("with_locale_en_us")
74 def test_parse_string_factory_invariance(x, parse_string_func, orig_func):
75 # parse_string_factory is the high-level combination of several dedicated
76 # functions involved in splitting and manipulating a string. The details of
77 # what those functions do is not relevant to testing parse_string_factory.
78 # What is relevant is that the form of the output matches the invariant
79 # that even elements are string and odd are numerical. That each component
80 # function is doing what it should is tested elsewhere.
81 value = "".join(map(py23_str, x)) # Convert the input to a single string.
82 result = parse_string_func(value)
83 result_types = list(map(type, result))
84 expected_types = [py23_str if i % 2 == 0 else float for i in range(len(result))]
85 assert result_types == expected_types
7486
87 # The result is in our CustomTuple.
88 assert isinstance(result, CustomTuple)
7589
76 def test_parse_string_factory_only_parses_digits_with_nosign_int_example():
77 assert _parse_string_factory(0, '', _int_nosign_re.split, no_op, fast_int, tuple2)('a5+5.034e-1') == ('a', 5, '+', 5, '.', 34, 'e-', 1)
78
79
80 @given(lists(elements=floats() | text().filter(whitespace_check) | integers(), min_size=1, max_size=10))
81 @example([10000000000000000000000000000000000000000000000000000000000000000000000000,
82 100000000000000000000000000000000000000000000000000000000000000000000000000,
83 100000000000000000000000000000000000000000000000000000000000000000000000000])
84 def test_parse_string_factory_only_parses_digits_with_nosign_int(x):
85 s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x)
86 assert _parse_string_factory(0, '', _int_nosign_re.split, no_op, fast_int, tuple2)(s) == int_splitter(s, False, '')
87
88
89 def test_parse_string_factory_parses_digit_with_sign_with_signed_int_example():
90 assert _parse_string_factory(0, '', _int_sign_re.split, no_op, fast_int, tuple2)('a5+5.034e-1') == ('a', 5, '', 5, '.', 34, 'e', -1)
91
92
93 @given(lists(elements=floats() | text().filter(whitespace_check) | integers(), min_size=1, max_size=10))
94 def test_parse_string_factory_parses_digit_with_sign_with_signed_int(x):
95 s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x)
96 assert _parse_string_factory(0, '', _int_sign_re.split, no_op, fast_int, tuple2)(s) == int_splitter(s, True, '')
97
98
99 def test_parse_string_factory_only_parses_float_with_nosign_noexp_float_example():
100 assert _parse_string_factory(0, '', _float_nosign_noexp_re.split, no_op, fast_float, tuple2)('a5+5.034e-1') == ('a', 5.0, '+', 5.034, 'e-', 1.0)
101
102
103 @given(lists(elements=floats(allow_nan=False) | text().filter(whitespace_check) | integers(), min_size=1, max_size=10))
104 def test_parse_string_factory_only_parses_float_with_nosign_noexp_float(x):
105 s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x)
106 assert _parse_string_factory(0, '', _float_nosign_noexp_re.split, no_op, fast_float, tuple2)(s) == float_splitter(s, False, False, '')
107
108
109 def test_parse_string_factory_only_parses_float_with_exponent_with_nosign_exp_float_example():
110 assert _parse_string_factory(0, '', _float_nosign_exp_re.split, no_op, fast_float, tuple2)('a5+5.034e-1') == ('a', 5.0, '+', 0.5034)
111
112
113 @given(lists(elements=floats(allow_nan=False) | text().filter(whitespace_check) | integers(), min_size=1, max_size=10))
114 def test_parse_string_factory_only_parses_float_with_exponent_with_nosign_exp_float(x):
115 s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x)
116 assert _parse_string_factory(0, '', _float_nosign_exp_re.split, no_op, fast_float, tuple2)(s) == float_splitter(s, False, True, '')
117
118
119 def test_parse_string_factory_only_parses_float_with_sign_with_sign_noexp_float_example():
120 assert _parse_string_factory(0, '', _float_sign_noexp_re.split, no_op, fast_float, tuple2)('a5+5.034e-1') == ('a', 5.0, '', 5.034, 'e', -1.0)
121
122
123 @given(lists(elements=floats(allow_nan=False) | text().filter(whitespace_check) | integers(), min_size=1, max_size=10))
124 def test_parse_string_factory_only_parses_float_with_sign_with_sign_noexp_float(x):
125 s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x)
126 assert _parse_string_factory(0, '', _float_sign_noexp_re.split, no_op, fast_float, tuple2)(s) == float_splitter(s, True, False, '')
127
128
129 def test_parse_string_factory_parses_float_with_sign_exp_float_example():
130 assert _parse_string_factory(0, '', _float_sign_exp_re.split, no_op, fast_float, tuple2)('a5+5.034e-1') == ('a', 5.0, '', 0.5034)
131 assert _parse_string_factory(0, '', _float_sign_exp_re.split, no_op, fast_float, tuple2)('6a5+5.034e-1') == ('', 6.0, 'a', 5.0, '', 0.5034)
132
133
134 @given(lists(elements=floats(allow_nan=False) | text().filter(whitespace_check) | integers(), min_size=1, max_size=10))
135 def test_parse_string_factory_parses_float_with_sign_exp_float(x):
136 s = ''.join(repr(y) if type(y) in (float, long, int) else y for y in x)
137 assert _parse_string_factory(0, '', _float_sign_exp_re.split, no_op, fast_float, tuple2)(s) == float_splitter(s, True, True, '')
138
139
140 def test_parse_string_factory_selects_pre_function_value_if_not_dumb():
141 def tuple2(x, orig):
142 """Make the input a tuple."""
143 return (orig[0], tuple(x))
144 assert _parse_string_factory(0, '', _int_nosign_re.split, py23_str.upper, fast_float, tuple2)('a5+5.034e-1') == ('A', ('A', 5, '+', 5, '.', 34, 'E-', 1))
145 assert _parse_string_factory(ns._DUMB, '', _int_nosign_re.split, py23_str.upper, fast_float, tuple2)('a5+5.034e-1') == ('A', ('A', 5, '+', 5, '.', 34, 'E-', 1))
146 assert _parse_string_factory(ns.LOCALE, '', _int_nosign_re.split, py23_str.upper, fast_float, tuple2)('a5+5.034e-1') == ('A', ('A', 5, '+', 5, '.', 34, 'E-', 1))
147 assert _parse_string_factory(ns.LOCALE | ns._DUMB, '', _int_nosign_re.split, py23_str.upper, fast_float, tuple2)('a5+5.034e-1') == ('a', ('A', 5, '+', 5, '.', 34, 'E-', 1))
148
149
150 def test_parse_path_function_parses_string_as_path_then_as_string():
151 splt = _parse_string_factory(0, '', _float_sign_exp_re.split, no_op, fast_float, tuple2)
152 assert _parse_path_factory(splt)('/p/Folder (10)/file34.5nm (2).tar.gz') == (('/',), ('p',), ('Folder (', 10.0, ')',), ('file', 34.5, 'nm (', 2.0, ')'), ('.tar',), ('.gz',))
153 assert _parse_path_factory(splt)('../Folder (10)/file (2).tar.gz') == (('..',), ('Folder (', 10.0, ')',), ('file (', 2.0, ')'), ('.tar',), ('.gz',))
154 assert _parse_path_factory(splt)('Folder (10)/file.f34.5nm (2).tar.gz') == (('Folder (', 10.0, ')',), ('file.f', 34.5, 'nm (', 2.0, ')'), ('.tar',), ('.gz',))
90 # Original should have gone through the "input_transform"
91 # which is uppercase in these tests.
92 assert result.original == orig_func(unicodedata.normalize("NFD", value))
0 # -*- coding: utf-8 -*-
1 """These test the splitting regular expressions."""
2 from __future__ import unicode_literals
3
4 import pytest
5 from natsort.utils import NumericalRegularExpressions as NumRegex
6
7
8 regex_names = {
9 NumRegex.int_nosign(): "int_nosign",
10 NumRegex.int_sign(): "int_sign",
11 NumRegex.float_nosign_noexp(): "float_nosign_noexp",
12 NumRegex.float_sign_noexp(): "float_sign_noexp",
13 NumRegex.float_nosign_exp(): "float_nosign_exp",
14 NumRegex.float_sign_exp(): "float_sign_exp",
15 }
16
17 # Regex Aliases (so lines stay a reasonable length.
18 i_u = NumRegex.int_nosign()
19 i_s = NumRegex.int_sign()
20 f_u = NumRegex.float_nosign_noexp()
21 f_s = NumRegex.float_sign_noexp()
22 f_ue = NumRegex.float_nosign_exp()
23 f_se = NumRegex.float_sign_exp()
24
25 # Assemble a test suite of regular strings and their regular expression
26 # splitting result. Organize by the input string.
27 regex_tests = {
28 "-123.45e+67": {
29 i_u: ["-", "123", ".", "45", "e+", "67", ""],
30 i_s: ["", "-123", ".", "45", "e", "+67", ""],
31 f_u: ["-", "123.45", "e+", "67", ""],
32 f_s: ["", "-123.45", "e", "+67", ""],
33 f_ue: ["-", "123.45e+67", ""],
34 f_se: ["", "-123.45e+67", ""],
35 },
36 "a-123.45e+67b": {
37 i_u: ["a-", "123", ".", "45", "e+", "67", "b"],
38 i_s: ["a", "-123", ".", "45", "e", "+67", "b"],
39 f_u: ["a-", "123.45", "e+", "67", "b"],
40 f_s: ["a", "-123.45", "e", "+67", "b"],
41 f_ue: ["a-", "123.45e+67", "b"],
42 f_se: ["a", "-123.45e+67", "b"],
43 },
44 "hello": {
45 i_u: ["hello"],
46 i_s: ["hello"],
47 f_u: ["hello"],
48 f_s: ["hello"],
49 f_ue: ["hello"],
50 f_se: ["hello"],
51 },
52 "abc12.34.56-7def": {
53 i_u: ["abc", "12", ".", "34", ".", "56", "-", "7", "def"],
54 i_s: ["abc", "12", ".", "34", ".", "56", "", "-7", "def"],
55 f_u: ["abc", "12.34", "", ".56", "-", "7", "def"],
56 f_s: ["abc", "12.34", "", ".56", "", "-7", "def"],
57 f_ue: ["abc", "12.34", "", ".56", "-", "7", "def"],
58 f_se: ["abc", "12.34", "", ".56", "", "-7", "def"],
59 },
60 "a1b2c3d4e5e6": {
61 i_u: ["a", "1", "b", "2", "c", "3", "d", "4", "e", "5", "e", "6", ""],
62 i_s: ["a", "1", "b", "2", "c", "3", "d", "4", "e", "5", "e", "6", ""],
63 f_u: ["a", "1", "b", "2", "c", "3", "d", "4", "e", "5", "e", "6", ""],
64 f_s: ["a", "1", "b", "2", "c", "3", "d", "4", "e", "5", "e", "6", ""],
65 f_ue: ["a", "1", "b", "2", "c", "3", "d", "4e5", "e", "6", ""],
66 f_se: ["a", "1", "b", "2", "c", "3", "d", "4e5", "e", "6", ""],
67 },
68 "eleven۱۱eleven11eleven১১": { # All of these are the decimal 11
69 i_u: ["eleven", "۱۱", "eleven", "11", "eleven", "১১", ""],
70 i_s: ["eleven", "۱۱", "eleven", "11", "eleven", "১১", ""],
71 f_u: ["eleven", "۱۱", "eleven", "11", "eleven", "১১", ""],
72 f_s: ["eleven", "۱۱", "eleven", "11", "eleven", "১১", ""],
73 f_ue: ["eleven", "۱۱", "eleven", "11", "eleven", "১১", ""],
74 f_se: ["eleven", "۱۱", "eleven", "11", "eleven", "১১", ""],
75 },
76 "12①②ⅠⅡ⅓": { # Two decimals, Two digits, Two numerals, fraction
77 i_u: ["", "12", "", "①", "", "②", "ⅠⅡ⅓"],
78 i_s: ["", "12", "", "①", "", "②", "ⅠⅡ⅓"],
79 f_u: ["", "12", "", "①", "", "②", "", "Ⅰ", "", "Ⅱ", "", "⅓", ""],
80 f_s: ["", "12", "", "①", "", "②", "", "Ⅰ", "", "Ⅱ", "", "⅓", ""],
81 f_ue: ["", "12", "", "①", "", "②", "", "Ⅰ", "", "Ⅱ", "", "⅓", ""],
82 f_se: ["", "12", "", "①", "", "②", "", "Ⅰ", "", "Ⅱ", "", "⅓", ""],
83 }
84 }
85
86
87 # From the above collections, create the parametrized tests and labels.
88 regex_params = [
89 (given, expected, regex)
90 for given, values in regex_tests.items()
91 for regex, expected in values.items()
92 ]
93 labels = ["{}-{}".format(given, regex_names[regex]) for given, _, regex in regex_params]
94
95
96 @pytest.mark.parametrize("x, expected, regex", regex_params, ids=labels)
97 def test_regex_splits_correctly(x, expected, regex):
98 # noinspection PyUnresolvedReferences
99 assert regex.split(x) == expected
11 """These test the utils.py functions."""
22 from __future__ import unicode_literals
33
4 from natsort.ns_enum import ns
5 from natsort.utils import (
6 _string_component_transform_factory,
7 _groupletters,
8 )
9 from natsort.compat.py23 import py23_str
4 from functools import partial
5
6 import pytest
7 from hypothesis import example, given
8 from hypothesis.strategies import floats, integers, text
9 from natsort.compat.fastnumbers import fast_float, fast_int
1010 from natsort.compat.locale import get_strxfrm
11 from natsort.compat.fastnumbers import (
12 fast_float,
13 fast_int,
14 )
15 from hypothesis import (
16 given,
17 )
18 from hypothesis.strategies import (
19 text,
20 floats,
21 integers,
22 )
23 from compat.locale import bad_uni_chars
11 from natsort.compat.py23 import py23_range, py23_str, py23_unichr
12 from natsort.ns_enum import ns, ns_DUMB
13 from natsort.utils import groupletters, string_component_transform_factory
14
15 # There are some unicode values that are known failures with the builtin locale
16 # library on BSD systems that has nothing to do with natsort (a ValueError is
17 # raised by strxfrm). Let's filter them out.
18 try:
19 bad_uni_chars = frozenset(
20 py23_unichr(x) for x in py23_range(0X10fefd, 0X10ffff + 1)
21 )
22 except ValueError:
23 # Narrow unicode build... no worries.
24 bad_uni_chars = frozenset()
25
26
27 def no_bad_uni_chars(x, _bad_chars=bad_uni_chars):
28 """Ensure text does not contain bad unicode characters"""
29 return not any(y in _bad_chars for y in x)
2430
2531
2632 def no_null(x):
27 return '\0' not in x
33 """Ensure text does not contain a null character."""
34 return "\0" not in x
2835
2936
30 # Each test has an "example" version for demonstrative purposes,
31 # and a test that uses the hypothesis module.
32
33
34 def test_string_component_transform_factory_returns_fast_int_example():
35 x = 'hello'
36 assert _string_component_transform_factory(0)(x) is fast_int(x)
37 assert _string_component_transform_factory(0)('5007') == fast_int('5007')
38
39
40 @given(text().filter(bool) | floats() | integers())
41 def test_string_component_transform_factory_returns_fast_int(x):
42 assert _string_component_transform_factory(0)(py23_str(x)) == fast_int(py23_str(x))
43
44
45 def test_string_component_transform_factory_with_FLOAT_returns_fast_float_example():
46 x = 'hello'
47 assert _string_component_transform_factory(ns.FLOAT)(x) is fast_float(x)
48 assert _string_component_transform_factory(ns.FLOAT)('5007') == fast_float('5007')
49
50
51 @given(text().filter(bool) | floats() | integers())
52 def test_string_component_transform_factory_with_FLOAT_returns_fast_float(x):
53 assert _string_component_transform_factory(ns.FLOAT)(py23_str(x)) == fast_float(py23_str(x), nan=float('-inf'))
54
55
56 def test_string_component_transform_factory_with_FLOAT_returns_fast_float_with_neg_inf_replacing_nan():
57 assert _string_component_transform_factory(ns.FLOAT)('nan') == fast_float('nan', nan=float('-inf'))
58
59
60 def test_string_component_transform_factory_with_FLOAT_and_NANLAST_returns_fast_float_with_pos_inf_replacing_nan():
61 assert _string_component_transform_factory(ns.FLOAT | ns.NANLAST)('nan') == fast_float('nan', nan=float('+inf'))
62
63
64 def test_string_component_transform_factory_with_GROUPLETTERS_returns_fast_int_and_groupletters_example():
65 x = 'hello'
66 assert _string_component_transform_factory(ns.GROUPLETTERS)(x) == fast_int(x, key=_groupletters)
67
68
69 @given(text().filter(bool))
70 def test_string_component_transform_factory_with_GROUPLETTERS_returns_fast_int_and_groupletters(x):
71 assert _string_component_transform_factory(ns.GROUPLETTERS)(x) == fast_int(x, key=_groupletters)
72
73
74 def test_string_component_transform_factory_with_LOCALE_returns_fast_int_and_groupletters_example():
75 x = 'hello'
76 assert _string_component_transform_factory(ns.LOCALE)(x) == fast_int(x, key=get_strxfrm())
77
78
79 @given(text().filter(bool).filter(lambda x: not any(y in bad_uni_chars for y in x)).filter(no_null))
80 def test_string_component_transform_factory_with_LOCALE_returns_fast_int_and_groupletters(x):
81 assert _string_component_transform_factory(ns.LOCALE)(x) == fast_int(x, key=get_strxfrm())
82
83
84 def test_string_component_transform_factory_with_LOCALE_and_GROUPLETTERS_returns_fast_int_and_groupletters_and_locale_convert_example():
85 x = 'hello'
86 assert _string_component_transform_factory(ns.GROUPLETTERS | ns.LOCALE)(x) == fast_int(x, key=lambda x: get_strxfrm()(_groupletters(x)))
87
88
89 @given(text().filter(bool).filter(no_null))
90 def test_string_component_transform_factory_with_LOCALE_and_GROUPLETTERS_returns_fast_int_and_groupletters_and_locale_convert(x):
37 @pytest.mark.parametrize(
38 "alg, example_func",
39 [
40 (ns.INT, fast_int),
41 (ns.DEFAULT, fast_int),
42 (ns.FLOAT, partial(fast_float, nan=float("-inf"))),
43 (ns.FLOAT | ns.NANLAST, partial(fast_float, nan=float("+inf"))),
44 (ns.GROUPLETTERS, partial(fast_int, key=groupletters)),
45 (ns.LOCALE, partial(fast_int, key=lambda x: get_strxfrm()(x))),
46 (
47 ns.GROUPLETTERS | ns.LOCALE,
48 partial(fast_int, key=lambda x: get_strxfrm()(groupletters(x))),
49 ),
50 (
51 ns_DUMB | ns.LOCALE,
52 partial(fast_int, key=lambda x: get_strxfrm()(groupletters(x))),
53 ),
54 (
55 ns.GROUPLETTERS | ns.LOCALE | ns.FLOAT | ns.NANLAST,
56 partial(
57 fast_float,
58 key=lambda x: get_strxfrm()(groupletters(x)),
59 nan=float("+inf"),
60 ),
61 ),
62 ],
63 )
64 @example(x=float("nan"))
65 @given(
66 x=integers()
67 | floats()
68 | text().filter(bool).filter(no_bad_uni_chars).filter(no_null)
69 )
70 @pytest.mark.usefixtures("with_locale_en_us")
71 def test_string_component_transform_factory(x, alg, example_func):
72 string_component_transform_func = string_component_transform_factory(alg)
9173 try:
92 assert _string_component_transform_factory(ns.GROUPLETTERS | ns.LOCALE)(x) == fast_int(x, key=lambda x: get_strxfrm()(_groupletters(x)))
74 assert string_component_transform_func(py23_str(x)) == example_func(py23_str(x))
9375 except ValueError as e: # handle broken locale lib on BSD.
94 if 'is not in range' not in str(e):
76 if "is not in range" not in str(e):
9577 raise
96
97
98 def test_string_component_transform_factory_with_LOCALE_and_DUMB_returns_fast_int_and_groupletters_and_locale_convert_example():
99 x = 'hello'
100 assert _string_component_transform_factory(ns._DUMB | ns.LOCALE)(x) == fast_int(x, key=lambda x: get_strxfrm()(_groupletters(x)))
101
102
103 @given(text().filter(bool).filter(no_null))
104 def test_string_component_transform_factory_with_LOCALE_and_DUMB_returns_fast_int_and_groupletters_and_locale_convert(x):
105 try:
106 assert _string_component_transform_factory(ns._DUMB | ns.LOCALE)(x) == fast_int(x, key=lambda x: get_strxfrm()(_groupletters(x)))
107 except ValueError as e: # handle broken locale lib on BSD.
108 if 'is not in range' not in str(e):
109 raise
22 Test the Unicode numbers module.
33 """
44 from __future__ import unicode_literals
5
56 import unicodedata
7
68 from natsort.compat.py23 import py23_range, py23_unichr
79 from natsort.unicode_numbers import (
8 numeric_hex,
9 numeric_chars,
10 numeric,
10 decimal_chars,
11 decimals,
1112 digit_chars,
1213 digits,
13 decimal_chars,
14 decimals,
1514 digits_no_decimals,
15 numeric,
16 numeric_chars,
17 numeric_hex,
1618 numeric_no_decimals,
1719 )
1820
4244 a = py23_unichr(i)
4345 except ValueError:
4446 break
45 if a in set('0123456789'):
47 if a in set("0123456789"):
4648 continue
4749 if unicodedata.numeric(a, None) is not None:
4850 assert i in set_numeric_hex
6264
6365
6466 def test_combined_string_contains_all_characters_in_list():
65 assert numeric == ''.join(numeric_chars)
66 assert digits == ''.join(digit_chars)
67 assert decimals == ''.join(decimal_chars)
67 assert numeric == "".join(numeric_chars)
68 assert digits == "".join(digit_chars)
69 assert decimals == "".join(decimal_chars)
55 import string
66 from itertools import chain
77 from operator import neg as op_neg
8 from pytest import raises
8
9 import pytest
10 from hypothesis import given
11 from hypothesis.strategies import integers, lists, sampled_from, text
12 from natsort import utils
13 from natsort.compat.py23 import py23_cmp, py23_int, py23_lower, py23_str
914 from natsort.ns_enum import ns
10 from natsort.utils import (
11 _sep_inserter,
12 _args_to_enum,
13 _regex_chooser,
14 _float_sign_exp_re,
15 _float_nosign_exp_re,
16 _float_sign_noexp_re,
17 _float_nosign_noexp_re,
18 _int_nosign_re,
19 _int_sign_re,
20 _do_decoding,
21 _path_splitter,
22 _groupletters,
23 chain_functions,
24 )
25 from natsort.compat.py23 import py23_str, py23_cmp
26 from natsort.compat.locale import null_string_locale
27 from slow_splitters import (
28 sep_inserter,
29 add_leading_space_if_first_is_num,
30 )
31 from compat.locale import low
32 from hypothesis import (
33 given,
34 )
35 from hypothesis.strategies import (
36 sampled_from,
37 lists,
38 text,
39 integers,
40 )
4115
4216
4317 def test_do_decoding_decodes_bytes_string_to_unicode():
44 assert type(_do_decoding(b'bytes', 'ascii')) is py23_str
45 assert _do_decoding(b'bytes', 'ascii') == 'bytes'
46 assert _do_decoding(b'bytes', 'ascii') == b'bytes'.decode('ascii')
18 assert type(utils.do_decoding(b"bytes", "ascii")) is py23_str
19 assert utils.do_decoding(b"bytes", "ascii") == "bytes"
20 assert utils.do_decoding(b"bytes", "ascii") == b"bytes".decode("ascii")
4721
4822
49 def test_args_to_enum_raises_TypeError_for_invalid_argument():
50 with raises(TypeError):
51 _args_to_enum(**{'alf': 0})
23 def test_args_to_enum_raises_typeerror_for_invalid_argument():
24 with pytest.raises(TypeError):
25 utils.args_to_enum(**{"alf": 0})
5226
5327
54 def test_args_to_enum_converts_signed_exp_float_to_ns_F():
55 # number_type, signed, exp, as_path, py3_safe
56 assert _args_to_enum(**{'number_type': float,
57 'signed': True,
58 'exp': True}) == ns.F | ns.S
28 @pytest.mark.parametrize(
29 "kwargs, expected",
30 [
31 ({"number_type": float, "signed": True, "exp": True}, ns.F | ns.S),
32 ({"number_type": float, "signed": True, "exp": False}, ns.F | ns.N | ns.S),
33 ({"number_type": float, "signed": False, "exp": True}, ns.F | ns.U),
34 ({"number_type": float, "signed": False, "exp": True}, ns.F),
35 ({"number_type": float, "signed": False, "exp": False}, ns.F | ns.U | ns.N),
36 ({"number_type": float, "as_path": True}, ns.F | ns.P),
37 ({"number_type": int, "as_path": True}, ns.I | ns.P),
38 ({"number_type": int, "signed": False}, ns.I | ns.U),
39 ({"number_type": None, "exp": True}, ns.I | ns.U),
40 ],
41 )
42 def test_args_to_enum(kwargs, expected):
43 with pytest.warns(DeprecationWarning):
44 assert utils.args_to_enum(**kwargs) == expected
5945
6046
61 def test_args_to_enum_converts_signed_noexp_float_to_ns_FN():
62 # number_type, signed, exp, as_path, py3_safe
63 assert _args_to_enum(**{'number_type': float,
64 'signed': True,
65 'exp': False}) == ns.F | ns.N | ns.S
47 @pytest.mark.parametrize(
48 "alg, expected",
49 [
50 (ns.I, utils.NumericalRegularExpressions.int_nosign()),
51 (ns.I | ns.N, utils.NumericalRegularExpressions.int_nosign()),
52 (ns.I | ns.S, utils.NumericalRegularExpressions.int_sign()),
53 (ns.I | ns.S | ns.N, utils.NumericalRegularExpressions.int_sign()),
54 (ns.F, utils.NumericalRegularExpressions.float_nosign_exp()),
55 (ns.F | ns.N, utils.NumericalRegularExpressions.float_nosign_noexp()),
56 (ns.F | ns.S, utils.NumericalRegularExpressions.float_sign_exp()),
57 (ns.F | ns.S | ns.N, utils.NumericalRegularExpressions.float_sign_noexp()),
58 ],
59 )
60 def test_regex_chooser_returns_correct_regular_expression_object(alg, expected):
61 assert utils.regex_chooser(alg).pattern == expected.pattern
6662
6763
68 def test_args_to_enum_converts_unsigned_exp_float_to_ns_FU():
69 # number_type, signed, exp, as_path, py3_safe
70 assert _args_to_enum(**{'number_type': float,
71 'signed': False,
72 'exp': True}) == ns.F | ns.U
73 # unsigned is default
74 assert _args_to_enum(**{'number_type': float,
75 'signed': False,
76 'exp': True}) == ns.F
77
78
79 def test_args_to_enum_converts_unsigned_unexp_float_to_ns_FNU():
80 # number_type, signed, exp, as_path, py3_safe
81 assert _args_to_enum(**{'number_type': float,
82 'signed': False,
83 'exp': False}) == ns.F | ns.U | ns.N
84
85
86 def test_args_to_enum_converts_float_and_path_and_py3safe_to_ns_FPT():
87 # number_type, signed, exp, as_path, py3_safe
88 assert _args_to_enum(**{'number_type': float,
89 'as_path': True,
90 'py3_safe': True}) == ns.F | ns.P | ns.T
91
92
93 def test_args_to_enum_converts_int_and_path_to_ns_IP():
94 # number_type, signed, exp, as_path, py3_safe
95 assert _args_to_enum(**{'number_type': int, 'as_path': True}) == ns.I | ns.P
96
97
98 def test_args_to_enum_converts_unsigned_int_and_py3safe_to_ns_IUT():
99 # number_type, signed, exp, as_path, py3_safe
100 assert _args_to_enum(**{'number_type': int,
101 'signed': False,
102 'py3_safe': True}) == ns.I | ns.U | ns.T
103
104
105 def test_args_to_enum_converts_None_to_ns_IU():
106 # number_type, signed, exp, as_path, py3_safe
107 assert _args_to_enum(**{'number_type': None,
108 'exp': True}) == ns.I | ns.U
109
110
111 def test_regex_chooser_returns_correct_regular_expression_object():
112 assert _regex_chooser[ns.INT] is _int_nosign_re
113 assert _regex_chooser[ns.INT | ns.NOEXP] is _int_nosign_re
114 assert _regex_chooser[ns.INT | ns.SIGNED] is _int_sign_re
115 assert _regex_chooser[ns.INT | ns.SIGNED | ns.NOEXP] is _int_sign_re
116 assert _regex_chooser[ns.FLOAT] is _float_nosign_exp_re
117 assert _regex_chooser[ns.FLOAT | ns.NOEXP] is _float_nosign_noexp_re
118 assert _regex_chooser[ns.FLOAT | ns.SIGNED] is _float_sign_exp_re
119 assert _regex_chooser[ns.FLOAT | ns.SIGNED | ns.NOEXP] is _float_sign_noexp_re
120
121
122 def test_ns_enum_values_have_are_as_expected():
123 # Defaults
124 assert ns.TYPESAFE == 0
125 assert ns.INT == 0
126 assert ns.VERSION == 0
127 assert ns.DIGIT == 0
128 assert ns.UNSIGNED == 0
129
130 # Aliases
131 assert ns.TYPESAFE == ns.T
132 assert ns.INT == ns.I
133 assert ns.VERSION == ns.V
134 assert ns.DIGIT == ns.D
135 assert ns.UNSIGNED == ns.U
136 assert ns.FLOAT == ns.F
137 assert ns.SIGNED == ns.S
138 assert ns.NOEXP == ns.N
139 assert ns.PATH == ns.P
140 assert ns.LOCALEALPHA == ns.LA
141 assert ns.LOCALENUM == ns.LN
142 assert ns.LOCALE == ns.L
143 assert ns.IGNORECASE == ns.IC
144 assert ns.LOWERCASEFIRST == ns.LF
145 assert ns.GROUPLETTERS == ns.G
146 assert ns.UNGROUPLETTERS == ns.UG
147 assert ns.CAPITALFIRST == ns.C
148 assert ns.UNGROUPLETTERS == ns.CAPITALFIRST
149 assert ns.NANLAST == ns.NL
150 assert ns.COMPATIBILITYNORMALIZE == ns.CN
151 assert ns.NUMAFTER == ns.NA
152
153 # Convenience
154 assert ns.LOCALE == ns.LOCALEALPHA | ns.LOCALENUM
155 assert ns.REAL == ns.FLOAT | ns.SIGNED
156 assert ns._NUMERIC_ONLY == ns.REAL | ns.NOEXP
64 @pytest.mark.parametrize(
65 "alg, value_or_alias",
66 [
67 # Defaults
68 (ns.DEFAULT, 0),
69 (ns.TYPESAFE, 0),
70 (ns.INT, 0),
71 (ns.VERSION, 0),
72 (ns.DIGIT, 0),
73 (ns.UNSIGNED, 0),
74 # Aliases
75 (ns.TYPESAFE, ns.T),
76 (ns.INT, ns.I),
77 (ns.VERSION, ns.V),
78 (ns.DIGIT, ns.D),
79 (ns.UNSIGNED, ns.U),
80 (ns.FLOAT, ns.F),
81 (ns.SIGNED, ns.S),
82 (ns.NOEXP, ns.N),
83 (ns.PATH, ns.P),
84 (ns.LOCALEALPHA, ns.LA),
85 (ns.LOCALENUM, ns.LN),
86 (ns.LOCALE, ns.L),
87 (ns.IGNORECASE, ns.IC),
88 (ns.LOWERCASEFIRST, ns.LF),
89 (ns.GROUPLETTERS, ns.G),
90 (ns.UNGROUPLETTERS, ns.UG),
91 (ns.CAPITALFIRST, ns.C),
92 (ns.UNGROUPLETTERS, ns.CAPITALFIRST),
93 (ns.NANLAST, ns.NL),
94 (ns.COMPATIBILITYNORMALIZE, ns.CN),
95 (ns.NUMAFTER, ns.NA),
96 # Convenience
97 (ns.LOCALE, ns.LOCALEALPHA | ns.LOCALENUM),
98 (ns.REAL, ns.FLOAT | ns.SIGNED),
99 ],
100 )
101 def test_ns_enum_values_and_aliases(alg, value_or_alias):
102 assert alg == value_or_alias
157103
158104
159105 def test_chain_functions_is_a_no_op_if_no_functions_are_given():
160106 x = 2345
161 assert chain_functions([])(x) is x
107 assert utils.chain_functions([])(x) is x
162108
163109
164110 def test_chain_functions_does_one_function_if_one_function_is_given():
165 x = '2345'
166 assert chain_functions([len])(x) == 4
111 x = "2345"
112 assert utils.chain_functions([len])(x) == 4
167113
168114
169115 def test_chain_functions_combines_functions_in_given_order():
170116 x = 2345
171 assert chain_functions([str, len, op_neg])(x) == -len(str(x))
117 assert utils.chain_functions([str, len, op_neg])(x) == -len(str(x))
172118
173119
174120 # Each test has an "example" version for demonstrative purposes,
175121 # and a test that uses the hypothesis module.
176122
123
177124 def test_groupletters_returns_letters_with_lowercase_transform_of_letter_example():
178 assert _groupletters('HELLO') == 'hHeElLlLoO'
179 assert _groupletters('hello') == 'hheelllloo'
125 assert utils.groupletters("HELLO") == "hHeElLlLoO"
126 assert utils.groupletters("hello") == "hheelllloo"
180127
181128
182129 @given(text().filter(bool))
183 def test_groupeletters_returns_letters_with_lowercase_transform_of_letter(x):
184 assert _groupletters(x) == ''.join(chain.from_iterable([low(y), y] for y in x))
130 def test_groupletters_returns_letters_with_lowercase_transform_of_letter(x):
131 assert utils.groupletters(x) == "".join(
132 chain.from_iterable([py23_lower(y), y] for y in x)
133 )
185134
186135
187136 def test_sep_inserter_does_nothing_if_no_numbers_example():
188 assert list(_sep_inserter(iter(['a', 'b', 'c']), '')) == ['a', 'b', 'c']
189 assert list(_sep_inserter(iter(['a']), '')) == ['a']
137 assert list(utils.sep_inserter(iter(["a", "b", "c"]), "")) == ["a", "b", "c"]
138 assert list(utils.sep_inserter(iter(["a"]), "")) == ["a"]
190139
191140
192141 def test_sep_inserter_does_nothing_if_only_one_number_example():
193 assert list(_sep_inserter(iter(['a', 5]), '')) == ['a', 5]
142 assert list(utils.sep_inserter(iter(["a", 5]), "")) == ["a", 5]
194143
195144
196145 def test_sep_inserter_inserts_separator_string_between_two_numbers_example():
197 assert list(_sep_inserter(iter([5, 9]), '')) == ['', 5, '', 9]
198 assert list(_sep_inserter(iter([5, 9]), null_string_locale)) == [null_string_locale, 5, null_string_locale, 9]
146 assert list(utils.sep_inserter(iter([5, 9]), "")) == ["", 5, "", 9]
199147
200148
201 @given(lists(elements=text().filter(bool) | integers()))
149 @given(lists(elements=text().filter(bool) | integers(), min_size=3))
202150 def test_sep_inserter_inserts_separator_between_two_numbers(x):
203 assert list(_sep_inserter(iter(x), '')) == list(add_leading_space_if_first_is_num(sep_inserter(x, ''), ''))
151 # Rather than just replicating the the results in a different
152 # algorithm, validate that the "shape" of the output is as expected.
153 result = list(utils.sep_inserter(iter(x), ""))
154 for i, pos in enumerate(result[1:-1], 1):
155 if pos == "":
156 assert isinstance(result[i - 1], py23_int)
157 assert isinstance(result[i + 1], py23_int)
204158
205159
206160 def test_path_splitter_splits_path_string_by_separator_example():
207 z = '/this/is/a/path'
208 assert tuple(_path_splitter(z)) == tuple(pathlib.Path(z).parts)
209 z = pathlib.Path('/this/is/a/path')
210 assert tuple(_path_splitter(z)) == tuple(pathlib.Path(z).parts)
161 z = "/this/is/a/path"
162 assert tuple(utils.path_splitter(z)) == tuple(pathlib.Path(z).parts)
163 z = pathlib.Path("/this/is/a/path")
164 assert tuple(utils.path_splitter(z)) == tuple(pathlib.Path(z).parts)
211165
212166
213167 @given(lists(sampled_from(string.ascii_letters), min_size=2).filter(all))
214168 def test_path_splitter_splits_path_string_by_separator(x):
215169 z = py23_str(pathlib.Path(*x))
216 assert tuple(_path_splitter(z)) == tuple(pathlib.Path(z).parts)
170 assert tuple(utils.path_splitter(z)) == tuple(pathlib.Path(z).parts)
217171
218172
219173 def test_path_splitter_splits_path_string_by_separator_and_removes_extension_example():
220 z = '/this/is/a/path/file.exe'
174 z = "/this/is/a/path/file.exe"
221175 y = tuple(pathlib.Path(z).parts)
222 assert tuple(_path_splitter(z)) == y[:-1] + (pathlib.Path(z).stem, pathlib.Path(z).suffix)
176 assert tuple(utils.path_splitter(z)) == y[:-1] + (
177 pathlib.Path(z).stem,
178 pathlib.Path(z).suffix,
179 )
223180
224181
225182 @given(lists(sampled_from(string.ascii_letters), min_size=3).filter(all))
226183 def test_path_splitter_splits_path_string_by_separator_and_removes_extension(x):
227 z = py23_str(pathlib.Path(*x[:-2])) + '.' + x[-1]
184 z = py23_str(pathlib.Path(*x[:-2])) + "." + x[-1]
228185 y = tuple(pathlib.Path(z).parts)
229 assert tuple(_path_splitter(z)) == y[:-1] + (pathlib.Path(z).stem, pathlib.Path(z).suffix)
186 assert tuple(utils.path_splitter(z)) == y[:-1] + (
187 pathlib.Path(z).stem,
188 pathlib.Path(z).suffix,
189 )
230190
231191
232192 @given(integers())
44
55 [tox]
66 envlist =
7 py27, py34, py35, py36, py37, pypy
7 flake8, py27, py34, py35, py36, py37, pypy
88 # Other valid evironments are:
99 # docs
1010 # release
2828 pytest README.rst docs/source/intro.rst docs/source/examples.rst
2929 pytest --doctest-modules {envsitepackagesdir}/natsort
3030 # Full test suite. Allow the user to pass command-line objects.
31 pytest --flakes --pep8 --tb=short --cov {envsitepackagesdir}/natsort --cov-report term-missing {posargs:}
31 pytest --tb=short --cov {envsitepackagesdir}/natsort --cov-report term-missing {posargs:}
32
33 # Check code quality.
34 [testenv:flake8]
35 deps =
36 flake8
37 flake8-import-order
38 flake8-bugbear
39 pep8-naming
40 commands = flake8
3241
3342 # Build documentation.
3443 [testenv:docs]
3847 commands =
3948 {envpython} setup.py build_sphinx
4049
50 # Release the code to PyPI
4151 [testenv:release]
4252 deps =
4353 twine