New upstream version 5.4.1
Agustin Henze
5 years ago
0 | 0 | language: python |
1 | matrix: | |
1 | ||
2 | jobs: | |
2 | 3 | include: |
3 | 4 | - python: "2.7" |
4 | 5 | dist: trusty |
40 | 41 | dist: xenial |
41 | 42 | sudo: true |
42 | 43 | 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 | |
43 | 50 | |
44 | 51 | install: |
45 | 52 | - pip install -U pip |
48 | 55 | script: |
49 | 56 | - tox |
50 | 57 | |
58 | stages: | |
59 | - code-quality | |
60 | - test | |
61 | ||
51 | 62 | after_success: |
52 | 63 | - coverage xml |
53 | 64 | - python-codacy-coverage -r coverage.xml |
0 | 0 | [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" | |
8 | 6 | pytest-faulthandler = {version = "*", platform_python_implementation = "== 'CPython'"} |
9 | 7 | |
10 | 8 | # These packages are standard on newer python versions. |
11 | 9 | pathlib = {version = "*", python_version = "< '3.4'"} |
12 | mock = {version = "*", python_version = "< '3.3'"} |
27 | 27 | - `Examples and Recipes <http://natsort.readthedocs.io/en/master/examples.html>`_ |
28 | 28 | - `How Does Natsort Work? <http://natsort.readthedocs.io/en/master/howitworks.html>`_ |
29 | 29 | - `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. | |
34 | 30 | |
35 | 31 | - `FAQ`_ |
36 | 32 | - `Optional Dependencies`_ |
335 | 331 | |
336 | 332 | The most efficient sorting can occur if you install the |
337 | 333 | `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. | |
339 | 335 | ``natsort`` will still run (efficiently) without the package, but if you need |
340 | 336 | to squeeze out that extra juice it is recommended you include this as a dependency. |
341 | 337 | ``natsort`` will not require (or check) that |
9 | 9 | :maxdepth: 2 |
10 | 10 | |
11 | 11 | natsort_keygen.rst |
12 | natsort_key.rst | |
12 | 13 | natsorted.rst |
13 | 14 | versorted.rst |
14 | 15 | humansorted.rst |
1 | 1 | |
2 | 2 | Changelog |
3 | 3 | --------- |
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. | |
4 | 19 | |
5 | 20 | 07-07-2018 v. 5.3.3 |
6 | 21 | +++++++++++++++++++ |
16 | 16 | # If extensions (or modules to document with autodoc) are in another directory, |
17 | 17 | # add these directories to sys.path here. If the directory is relative to the |
18 | 18 | # 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('.')) | |
20 | 20 | |
21 | 21 | # -- General configuration ------------------------------------------------ |
22 | 22 | |
23 | 23 | # If your documentation needs a minimal Sphinx version, state it here. |
24 | #needs_sphinx = '1.0' | |
24 | # needs_sphinx = '1.0' | |
25 | 25 | |
26 | 26 | # Add any Sphinx extension module names here, as strings. They can be |
27 | 27 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom |
41 | 41 | source_suffix = '.rst' |
42 | 42 | |
43 | 43 | # The encoding of source files. |
44 | #source_encoding = 'utf-8-sig' | |
44 | # source_encoding = 'utf-8-sig' | |
45 | 45 | |
46 | 46 | # The master toctree document. |
47 | 47 | master_doc = 'index' |
48 | 48 | |
49 | 49 | # General information about the project. |
50 | 50 | project = u'natsort' |
51 | # noinspection PyShadowingBuiltins | |
51 | 52 | copyright = u'2014, Seth M. Morton' |
52 | 53 | |
53 | 54 | # The version info for the project you're documenting, acts as replacement for |
55 | 56 | # built documents. |
56 | 57 | # |
57 | 58 | # The full version, including alpha/beta/rc tags. |
58 | release = '5.3.3' | |
59 | release = '5.4.1' | |
59 | 60 | # The short X.Y version. |
60 | 61 | version = '.'.join(release.split('.')[0:2]) |
61 | 62 | |
62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation |
63 | 64 | # for a list of supported languages. |
64 | #language = None | |
65 | # language = None | |
65 | 66 | |
66 | 67 | # There are two options for replacing |today|: either, you set today to some |
67 | 68 | # non-false value, then it is used: |
68 | #today = '' | |
69 | # today = '' | |
69 | 70 | # Else, today_fmt is used as the format for a strftime call. |
70 | #today_fmt = '%B %d, %Y' | |
71 | # today_fmt = '%B %d, %Y' | |
71 | 72 | |
72 | 73 | # List of patterns, relative to source directory, that match files and |
73 | 74 | # directories to ignore when looking for source files. |
75 | 76 | |
76 | 77 | # The reST default role (used for this markup: `text`) to use for all |
77 | 78 | # documents. |
78 | #default_role = None | |
79 | # default_role = None | |
79 | 80 | |
80 | 81 | # If true, '()' will be appended to :func: etc. cross-reference text. |
81 | #add_function_parentheses = True | |
82 | # add_function_parentheses = True | |
82 | 83 | |
83 | 84 | # If true, the current module name will be prepended to all description |
84 | 85 | # unit titles (such as .. function::). |
85 | #add_module_names = True | |
86 | # add_module_names = True | |
86 | 87 | |
87 | 88 | # If true, sectionauthor and moduleauthor directives will be shown in the |
88 | 89 | # output. They are ignored by default. |
89 | #show_authors = False | |
90 | # show_authors = False | |
90 | 91 | |
91 | 92 | # The name of the Pygments (syntax highlighting) style to use. |
92 | 93 | pygments_style = 'sphinx' |
93 | 94 | highlight_language = 'python' |
94 | 95 | |
95 | 96 | # A list of ignored prefixes for module index sorting. |
96 | #modindex_common_prefix = [] | |
97 | # modindex_common_prefix = [] | |
97 | 98 | |
98 | 99 | # If true, keep warnings as "system message" paragraphs in the built documents. |
99 | #keep_warnings = False | |
100 | # keep_warnings = False | |
100 | 101 | |
101 | 102 | |
102 | 103 | # -- Options for HTML output ---------------------------------------------- |
108 | 109 | html_theme = 'default' |
109 | 110 | else: |
110 | 111 | import sphinx_rtd_theme |
112 | ||
111 | 113 | html_theme = 'sphinx_rtd_theme' |
112 | 114 | # html_theme = 'solar' |
113 | 115 | |
114 | 116 | # Theme options are theme-specific and customize the look and feel of a theme |
115 | 117 | # further. For a list of options available for each theme, see the |
116 | 118 | # documentation. |
117 | #html_theme_options = {} | |
119 | # html_theme_options = {} | |
118 | 120 | |
119 | 121 | # Add any paths that contain custom themes here, relative to this directory. |
120 | 122 | html_theme_path = ['.'] |
121 | 123 | |
122 | 124 | # The name for this set of Sphinx documents. If None, it defaults to |
123 | 125 | # "<project> v<release> documentation". |
124 | #html_title = None | |
126 | # html_title = None | |
125 | 127 | |
126 | 128 | # A shorter title for the navigation bar. Default is the same as html_title. |
127 | #html_short_title = None | |
129 | # html_short_title = None | |
128 | 130 | |
129 | 131 | # The name of an image file (relative to this directory) to place at the top |
130 | 132 | # of the sidebar. |
131 | #html_logo = None | |
133 | # html_logo = None | |
132 | 134 | |
133 | 135 | # The name of an image file (within the static path) to use as favicon of the |
134 | 136 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 |
135 | 137 | # pixels large. |
136 | #html_favicon = None | |
138 | # html_favicon = None | |
137 | 139 | |
138 | 140 | # Add any paths that contain custom static files (such as style sheets) here, |
139 | 141 | # relative to this directory. They are copied after the builtin static files, |
143 | 145 | # Add any extra paths that contain custom files (such as robots.txt or |
144 | 146 | # .htaccess) here, relative to this directory. These files are copied |
145 | 147 | # directly to the root of the documentation. |
146 | #html_extra_path = [] | |
148 | # html_extra_path = [] | |
147 | 149 | |
148 | 150 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, |
149 | 151 | # using the given strftime format. |
150 | #html_last_updated_fmt = '%b %d, %Y' | |
152 | # html_last_updated_fmt = '%b %d, %Y' | |
151 | 153 | |
152 | 154 | # If true, SmartyPants will be used to convert quotes and dashes to |
153 | 155 | # typographically correct entities. |
154 | #html_use_smartypants = True | |
156 | # html_use_smartypants = True | |
155 | 157 | |
156 | 158 | # Custom sidebar templates, maps document names to template names. |
157 | #html_sidebars = {} | |
159 | # html_sidebars = {} | |
158 | 160 | |
159 | 161 | # Additional templates that should be rendered to pages, maps page names to |
160 | 162 | # template names. |
161 | #html_additional_pages = {} | |
163 | # html_additional_pages = {} | |
162 | 164 | |
163 | 165 | # If false, no module index is generated. |
164 | #html_domain_indices = True | |
166 | # html_domain_indices = True | |
165 | 167 | |
166 | 168 | # If false, no index is generated. |
167 | #html_use_index = True | |
169 | # html_use_index = True | |
168 | 170 | |
169 | 171 | # If true, the index is split into individual pages for each letter. |
170 | #html_split_index = False | |
172 | # html_split_index = False | |
171 | 173 | |
172 | 174 | # If true, links to the reST sources are added to the pages. |
173 | #html_show_sourcelink = True | |
175 | # html_show_sourcelink = True | |
174 | 176 | |
175 | 177 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. |
176 | #html_show_sphinx = True | |
178 | # html_show_sphinx = True | |
177 | 179 | |
178 | 180 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. |
179 | #html_show_copyright = True | |
181 | # html_show_copyright = True | |
180 | 182 | |
181 | 183 | # If true, an OpenSearch description file will be output, and all pages will |
182 | 184 | # contain a <link> tag referring to it. The value of this option must be the |
183 | 185 | # base URL from which the finished HTML is served. |
184 | #html_use_opensearch = '' | |
186 | # html_use_opensearch = '' | |
185 | 187 | |
186 | 188 | # This is the file name suffix for HTML files (e.g. ".xhtml"). |
187 | #html_file_suffix = None | |
189 | # html_file_suffix = None | |
188 | 190 | |
189 | 191 | # Output file base name for HTML help builder. |
190 | 192 | htmlhelp_basename = 'natsortdoc' |
191 | 193 | |
192 | ||
193 | 194 | # -- Options for LaTeX output --------------------------------------------- |
194 | 195 | |
195 | 196 | 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': '', | |
204 | 205 | } |
205 | 206 | |
206 | 207 | # Grouping the document tree into LaTeX files. List of tuples |
207 | 208 | # (source start file, target name, title, |
208 | 209 | # author, documentclass [howto, manual, or own class]). |
209 | 210 | 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'), | |
212 | 213 | ] |
213 | 214 | |
214 | 215 | # The name of an image file (relative to this directory) to place at the top of |
215 | 216 | # the title page. |
216 | #latex_logo = None | |
217 | # latex_logo = None | |
217 | 218 | |
218 | 219 | # For "manual" documents, if this is true, then toplevel headings are parts, |
219 | 220 | # not chapters. |
220 | #latex_use_parts = False | |
221 | # latex_use_parts = False | |
221 | 222 | |
222 | 223 | # If true, show page references after internal links. |
223 | #latex_show_pagerefs = False | |
224 | # latex_show_pagerefs = False | |
224 | 225 | |
225 | 226 | # If true, show URL addresses after external links. |
226 | #latex_show_urls = False | |
227 | # latex_show_urls = False | |
227 | 228 | |
228 | 229 | # Documents to append as an appendix to all manuals. |
229 | #latex_appendices = [] | |
230 | # latex_appendices = [] | |
230 | 231 | |
231 | 232 | # If false, no module index is generated. |
232 | #latex_domain_indices = True | |
233 | # latex_domain_indices = True | |
233 | 234 | |
234 | 235 | |
235 | 236 | # -- Options for manual page output --------------------------------------- |
242 | 243 | ] |
243 | 244 | |
244 | 245 | # If true, show URL addresses after external links. |
245 | #man_show_urls = False | |
246 | # man_show_urls = False | |
246 | 247 | |
247 | 248 | |
248 | 249 | # -- Options for Texinfo output ------------------------------------------- |
251 | 252 | # (source start file, target name, title, author, |
252 | 253 | # dir menu entry, description, category) |
253 | 254 | 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'), | |
257 | 258 | ] |
258 | 259 | |
259 | 260 | # Documents to append as an appendix to all manuals. |
260 | #texinfo_appendices = [] | |
261 | # texinfo_appendices = [] | |
261 | 262 | |
262 | 263 | # If false, no module index is generated. |
263 | #texinfo_domain_indices = True | |
264 | # texinfo_domain_indices = True | |
264 | 265 | |
265 | 266 | # How to display URL addresses: 'footnote', 'no', or 'inline'. |
266 | #texinfo_show_urls = 'footnote' | |
267 | # texinfo_show_urls = 'footnote' | |
267 | 268 | |
268 | 269 | # If true, do not generate a @detailmenu in the "Top" node's menu. |
269 | #texinfo_no_detailmenu = False | |
270 | # texinfo_no_detailmenu = False | |
270 | 271 | |
271 | 272 | |
272 | 273 | # Example configuration for intersphinx: refer to the Python standard library. |
145 | 145 | Starting with :mod:`natsort` version 4.0.0 the default number definition was |
146 | 146 | changed to an *unsigned integer* which satisfies the "least astonishment" principle, and |
147 | 147 | 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... | |
170 | 148 | |
171 | 149 | Coercing Strings Containing Numbers Into Numbers |
172 | 150 | ++++++++++++++++++++++++++++++++++++++++++++++++ |
494 | 472 | |
495 | 473 | .. code-block:: python |
496 | 474 | |
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']), '')) | |
499 | 477 | ['apples'] |
500 | 478 | >>> |
501 | >>> list(_sep_inserter(iter([12, ' apples']), '')) | |
479 | >>> list(sep_inserter(iter([12, ' apples']), '')) | |
502 | 480 | ['', 12, ' apples'] |
503 | 481 | >>> |
504 | >>> list(_sep_inserter(iter(['version', 5, -3]), '')) | |
482 | >>> list(sep_inserter(iter(['version', 5, -3]), '')) | |
505 | 483 | ['version', 5, '', -3] |
506 | 484 | >>> |
507 | 485 | >>> from natsort import natsort_keygen, ns |
612 | 590 | ... coerced_input = (coerce_to_float(s) for s in split_input) |
613 | 591 | ... else: |
614 | 592 | ... 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, '')) | |
616 | 594 | ... |
617 | 595 | |
618 | 596 | And this doesn't even show handling :class:`bytes` type! Notice that we have |
1091 | 1069 | .. [#f3] |
1092 | 1070 | I'm not going to show how this is implemented in this document, |
1093 | 1071 | 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`_. | |
1095 | 1073 | .. [#f4] |
1096 | 1074 | Handling each of these is straightforward, but coupled with the rapidly |
1097 | 1075 | fracturing execution paths presented in :ref:`TL;DR 2 <tldr2>` one can imagine |
329 | 329 | |
330 | 330 | The most efficient sorting can occur if you install the |
331 | 331 | `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. | |
333 | 333 | :mod:`natsort` will still run (efficiently) without the package, but if you need |
334 | 334 | to squeeze out that extra juice it is recommended you include this as a dependency. |
335 | 335 | :mod:`natsort` will not require (or check) that |
68 | 68 | |
69 | 69 | .. _bug_note: |
70 | 70 | |
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 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | |
73 | 73 | |
74 | 74 | It's not Python's fault, but the OS... the locale library for BSD-based systems |
75 | 75 | (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 |
0 | 0 | # -*- 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 | |
7 | 2 | |
8 | # Local imports. | |
9 | 3 | import sys |
10 | 4 | |
11 | from natsort.utils import chain_functions | |
12 | from natsort._version import __version__ | |
13 | ||
14 | 5 | 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, | |
15 | 14 | natsort_key, |
16 | 15 | natsort_keygen, |
17 | 16 | natsorted, |
17 | ns, | |
18 | order_by_index, | |
19 | realsorted, | |
18 | 20 | 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, | |
30 | 21 | ) |
22 | from natsort.utils import chain_functions | |
31 | 23 | |
32 | 24 | if float(sys.version[:3]) < 3: |
33 | 25 | from natsort.natsort import natcmp |
34 | 26 | |
27 | __version__ = "5.4.1" | |
28 | ||
35 | 29 | __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", | |
53 | 47 | ] |
54 | 48 | |
55 | 49 | # 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())) |
0 | 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. | |
1 | from __future__ import absolute_import, division, print_function, unicode_literals | |
2 | ||
9 | 3 | import sys |
10 | 4 | |
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 | |
15 | 6 | 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 | """ | |
20 | 12 | 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. | |
23 | 15 | """ |
24 | 16 | |
25 | 17 | from argparse import ArgumentParser, RawDescriptionHelpFormatter |
26 | 18 | 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", | |
60 | 82 | 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", | |
69 | 95 | 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", | |
73 | 104 | 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) | |
89 | 132 | |
90 | 133 | # 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) | |
93 | 136 | |
94 | 137 | # Remove trailing whitespace from all the entries |
95 | 138 | entries = [e.strip() for e in args.entries] |
99 | 142 | |
100 | 143 | |
101 | 144 | 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 | ||
106 | 162 | """ |
107 | 163 | if low >= high: |
108 | raise ValueError('low >= high') | |
164 | raise ValueError("low >= high") | |
109 | 165 | else: |
110 | 166 | return low, high |
111 | 167 | |
112 | 168 | |
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: | |
122 | 190 | return None |
123 | 191 | 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)) | |
127 | 195 | |
128 | 196 | |
129 | 197 | 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 | ||
149 | 250 | """ |
150 | 251 | return not any(converter(num) in values for num in regex.findall(entry)) |
151 | 252 | |
154 | 255 | """Sort the entries, applying the filters first if necessary.""" |
155 | 256 | |
156 | 257 | # 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 | ) | |
164 | 267 | |
165 | 268 | # Pre-remove entries that don't pass the filtering criteria |
166 | 269 | # Make sure we use the same searching algorithm for filtering |
167 | 270 | # as for sorting. |
168 | 271 | do_filter = args.filter is not None or args.reverse_filter is not None |
169 | 272 | 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) | |
175 | 279 | 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 | ] | |
181 | 286 | 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 | ] | |
187 | 296 | if args.exclude: |
188 | 297 | 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 | ] | |
192 | 303 | |
193 | 304 | # 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): | |
195 | 306 | print(entry) |
196 | 307 | |
197 | 308 | |
198 | if __name__ == '__main__': | |
309 | if __name__ == "__main__": | |
199 | 310 | try: |
200 | 311 | main() |
201 | 312 | except ValueError as a: |
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' |
0 | 0 | # -*- coding: utf-8 -*- |
1 | """\ | |
1 | """ | |
2 | 2 | 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. | |
5 | 4 | """ |
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 | |
12 | 6 | |
13 | 7 | # Std. lib imports. |
14 | 8 | import unicodedata |
9 | ||
10 | # Local imports. | |
11 | from natsort.compat.py23 import PY_VERSION | |
15 | 12 | from natsort.unicode_numbers import decimal_chars |
16 | from natsort.compat.py23 import PY_VERSION | |
13 | ||
17 | 14 | if PY_VERSION >= 3: |
18 | 15 | long = int |
19 | 16 | |
20 | 17 | |
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]) | |
24 | 37 | NAN_INF = frozenset(NAN_INF) |
25 | ASCII_NUMS = '0123456789+-' | |
38 | ASCII_NUMS = "0123456789+-" | |
39 | POTENTIAL_FIRST_CHAR = frozenset(decimal_chars + list(ASCII_NUMS + ".")) | |
26 | 40 | |
27 | 41 | |
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 | """ | |
32 | 52 | Convert a string to a float quickly, return input as-is if not possible. |
53 | ||
33 | 54 | 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 | ||
35 | 70 | """ |
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: | |
37 | 72 | try: |
38 | 73 | x = float(x) |
39 | 74 | return nan if nan is not None and x != x else x |
40 | 75 | except ValueError: |
41 | 76 | 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) | |
43 | 78 | except TypeError: # pragma: no cover |
44 | 79 | return key(x) |
45 | 80 | else: |
46 | 81 | 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) | |
48 | 83 | except TypeError: # pragma: no cover |
49 | 84 | return key(x) |
50 | 85 | |
51 | 86 | |
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 | """ | |
55 | 95 | Convert a string to a int quickly, return input as-is if not possible. |
96 | ||
56 | 97 | 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 | ||
58 | 111 | """ |
59 | 112 | if x[0] in _first_char: |
60 | 113 | try: |
61 | 114 | return long(x) |
62 | 115 | except ValueError: |
63 | 116 | 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) | |
65 | 118 | except TypeError: # pragma: no cover |
66 | 119 | return key(x) |
67 | 120 | else: |
68 | 121 | 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) | |
70 | 123 | except TypeError: # pragma: no cover |
71 | 124 | return key(x) |
0 | 0 | # -*- 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 | |
7 | 6 | |
8 | 7 | from distutils.version import StrictVersion |
9 | 8 | |
10 | 9 | # If the user has fastnumbers installed, they will get great speed |
11 | 10 | # benefits. If not, we use the simulated functions that come with natsort. |
12 | 11 | 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"): | |
20 | 17 | raise ImportError # pragma: no cover |
21 | 18 | 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 |
0 | 0 | # -*- 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 | |
7 | 6 | |
8 | 7 | # Std. lib imports. |
9 | 8 | import sys |
10 | 9 | |
11 | 10 | # 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 | |
17 | 12 | |
18 | 13 | # This string should be sorted after any other byte string because |
19 | 14 | # it contains the max unicode character repeated 20 times. |
20 | 15 | # You would need some odd data to come after that. |
21 | null_string = '' | |
16 | null_string = "" | |
22 | 17 | null_string_max = py23_unichr(sys.maxunicode) * 20 |
23 | 18 | |
24 | 19 | # Make the strxfrm function from strcoll on Python2 |
25 | 20 | # It can be buggy (especially on BSD-based systems), |
26 | 21 | # so prefer icu if available. |
27 | try: | |
22 | try: # noqa: C901 | |
28 | 23 | import icu |
29 | 24 | from locale import getlocale |
30 | 25 | |
31 | null_string_locale = b'' | |
26 | null_string_locale = b"" | |
32 | 27 | |
33 | 28 | # This string should in theory be sorted after any other byte |
34 | 29 | # string because it contains the max byte char repeated many times. |
35 | 30 | # 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 | |
37 | 32 | |
38 | 33 | def dumb_sort(): |
39 | 34 | return False |
41 | 36 | # If using icu, get the locale from the current global locale, |
42 | 37 | def get_icu_locale(): |
43 | 38 | try: |
44 | return icu.Locale('.'.join(getlocale())) | |
39 | return icu.Locale(".".join(getlocale())) | |
45 | 40 | except TypeError: # pragma: no cover |
46 | 41 | return icu.Locale() |
47 | 42 | |
56 | 51 | sep = icu.DecimalFormatSymbols.kDecimalSeparatorSymbol |
57 | 52 | return icu.DecimalFormatSymbols(get_icu_locale()).getSymbol(sep) |
58 | 53 | |
54 | ||
59 | 55 | except ImportError: |
60 | 56 | import locale |
57 | ||
61 | 58 | if PY_VERSION < 3: |
62 | 59 | from locale import strcoll |
60 | ||
63 | 61 | sentinel = object() |
64 | 62 | |
65 | 63 | def custom_strcoll(a, b, last=sentinel): |
72 | 70 | return strcoll(a, b) |
73 | 71 | |
74 | 72 | strxfrm = cmp_to_key(custom_strcoll) |
75 | null_string_locale = strxfrm('') | |
73 | null_string_locale = strxfrm("") | |
76 | 74 | null_string_locale_max = strxfrm(sentinel) |
77 | 75 | else: |
78 | 76 | from locale import strxfrm |
79 | null_string_locale = '' | |
77 | ||
78 | null_string_locale = "" | |
80 | 79 | |
81 | 80 | # This string should be sorted after any other byte string because |
82 | 81 | # it contains the max unicode character repeated 20 times. |
86 | 85 | # On some systems, locale is broken and does not sort in the expected |
87 | 86 | # order. We will try to detect this and compensate. |
88 | 87 | def dumb_sort(): |
89 | return strxfrm('A') < strxfrm('a') | |
88 | return strxfrm("A") < strxfrm("a") | |
90 | 89 | |
91 | 90 | def get_strxfrm(): |
92 | 91 | return strxfrm |
93 | 92 | |
94 | 93 | def get_thousands_sep(): |
95 | sep = locale.localeconv()['thousands_sep'] | |
94 | sep = locale.localeconv()["thousands_sep"] | |
96 | 95 | # If this locale library is broken, some of the thousands separator |
97 | 96 | # characters are incorrectly blank. Here is a lookup table of the |
98 | 97 | # corrections I am aware of. |
99 | 98 | if dumb_sort(): |
100 | 99 | try: |
101 | loc = '.'.join(locale.getlocale()) | |
100 | loc = ".".join(locale.getlocale()) | |
102 | 101 | 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) | |
138 | 138 | else: |
139 | 139 | return sep |
140 | 140 | |
141 | 141 | def get_decimal_point(): |
142 | return locale.localeconv()['decimal_point'] | |
142 | return locale.localeconv()["decimal_point"] |
0 | 0 | # -*- 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 | |
7 | 2 | |
8 | 3 | try: |
9 | 4 | from pathlib import PurePath # PurePath is the base object for Paths. |
0 | 0 | # -*- 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 | |
7 | 8 | |
8 | 9 | import functools |
9 | 10 | import sys |
18 | 19 | NEWPY = PY_VERSION >= 3.3 |
19 | 20 | |
20 | 21 | # 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 | |
22 | 23 | |
23 | 24 | # 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 | |
25 | 26 | |
26 | 27 | # 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) | |
28 | 32 | |
29 | 33 | # 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 | |
31 | 38 | |
32 | 39 | |
33 | 40 | def _py23_cmp(a, b): |
34 | 41 | return (a > b) - (a < b) |
35 | 42 | |
36 | 43 | |
37 | py23_cmp = _py23_cmp if sys.version[0] == '3' else cmp | |
44 | py23_cmp = _py23_cmp if PY_VERSION >= 3 else cmp | |
38 | 45 | |
39 | 46 | # zip as an iterator |
40 | if sys.version[0] == '3': | |
47 | if PY_VERSION >= 3: | |
41 | 48 | py23_zip = zip |
42 | 49 | py23_map = map |
43 | 50 | py23_filter = filter |
44 | 51 | else: |
45 | 52 | import itertools |
53 | ||
46 | 54 | py23_zip = itertools.izip |
47 | 55 | py23_map = itertools.imap |
48 | 56 | py23_filter = itertools.ifilter |
49 | ||
50 | 57 | |
51 | 58 | # cmp_to_key was not created till 2.7, so require this for 2.6 |
52 | 59 | try: |
53 | 60 | from functools import cmp_to_key |
54 | 61 | except ImportError: # pragma: no cover |
62 | ||
55 | 63 | def cmp_to_key(mycmp): |
56 | 64 | """Convert a cmp= function into a key= function""" |
65 | ||
57 | 66 | class K(object): |
58 | __slots__ = ['obj'] | |
67 | __slots__ = ["obj"] | |
59 | 68 | |
60 | 69 | def __init__(self, obj): |
61 | 70 | self.obj = obj |
79 | 88 | return mycmp(self.obj, other.obj) != 0 |
80 | 89 | |
81 | 90 | def __hash__(self): |
82 | raise TypeError('hash not implemented') | |
91 | raise TypeError("hash not implemented") | |
83 | 92 | |
84 | 93 | return K |
85 | 94 | |
103 | 112 | func.__doc__ = doc |
104 | 113 | return func |
105 | 114 | return doc |
115 | ||
106 | 116 | return wrapper |
107 | 117 | |
108 | 118 | |
109 | 119 | # Properly modify a doctstring to either have the unicode literal or not. |
110 | if sys.version[0] == '3': | |
120 | if PY_VERSION >= 3: | |
111 | 121 | # Abstract u'abc' syntax: |
112 | 122 | @_modify_str_or_docstring |
113 | 123 | def u_format(s): |
114 | 124 | """"{u}'abc'" --> "'abc'" (Python 3) |
115 | 125 | |
116 | 126 | 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 | ||
118 | 130 | else: |
119 | 131 | # Abstract u'abc' syntax: |
120 | 132 | @_modify_str_or_docstring |
122 | 134 | """"{u}'abc'" --> "u'abc'" (Python 2) |
123 | 135 | |
124 | 136 | 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") |
0 | 0 | # -*- coding: utf-8 -*- |
1 | 1 | """ |
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. | |
11 | 6 | """ |
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 | |
20 | 11 | from operator import itemgetter |
21 | from functools import partial | |
22 | from warnings import warn | |
23 | ||
24 | # Local imports. | |
25 | import sys | |
26 | 12 | |
27 | 13 | 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 | |
49 | 17 | |
50 | 18 | |
51 | 19 | @u_format |
55 | 23 | |
56 | 24 | Parameters |
57 | 25 | ---------- |
58 | encoding: str | |
26 | encoding : str | |
59 | 27 | The codec to use for decoding. This must be a valid unicode codec. |
60 | 28 | |
61 | 29 | Returns |
62 | 30 | ------- |
63 | decode_function: | |
31 | decode_function | |
64 | 32 | A function that takes a single argument and attempts to decode |
65 | 33 | it using the supplied codec. Any `UnicodeErrors` are raised. |
66 | 34 | If the argument was not of `bytes` type, it is simply returned |
87 | 55 | True |
88 | 56 | |
89 | 57 | """ |
90 | return partial(_do_decoding, encoding=encoding) | |
58 | return partial(utils.do_decoding, encoding=encoding) | |
91 | 59 | |
92 | 60 | |
93 | 61 | @u_format |
97 | 65 | |
98 | 66 | Parameters |
99 | 67 | ---------- |
100 | s: | |
101 | Any object. | |
102 | ||
103 | Returns | |
104 | ------- | |
105 | output: | |
68 | s : object | |
69 | ||
70 | Returns | |
71 | ------- | |
72 | output | |
106 | 73 | If the input was of type `bytes`, the return value is a `str` decoded |
107 | 74 | with the ASCII codec. Otherwise, the return value is identically the |
108 | 75 | input. |
112 | 79 | decoder |
113 | 80 | |
114 | 81 | """ |
115 | return _do_decoding(s, 'ascii') | |
82 | return utils.do_decoding(s, "ascii") | |
116 | 83 | |
117 | 84 | |
118 | 85 | @u_format |
122 | 89 | |
123 | 90 | Parameters |
124 | 91 | ---------- |
125 | s: | |
126 | Any object. | |
127 | ||
128 | Returns | |
129 | ------- | |
130 | output: | |
92 | s : object | |
93 | ||
94 | Returns | |
95 | ------- | |
96 | output | |
131 | 97 | If the input was of type `bytes`, the return value is a `str` decoded |
132 | 98 | with the UTF-8 codec. Otherwise, the return value is identically the |
133 | 99 | input. |
137 | 103 | decoder |
138 | 104 | |
139 | 105 | """ |
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 | """ | |
153 | 112 | Generate a key to sort strings and numbers naturally. |
154 | 113 | |
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. | |
158 | 116 | |
159 | 117 | The user may customize the generated function with the |
160 | 118 | arguments to `natsort_keygen`, including an optional |
182 | 140 | See Also |
183 | 141 | -------- |
184 | 142 | natsorted |
143 | natsort_key | |
185 | 144 | |
186 | 145 | Examples |
187 | 146 | -------- |
196 | 155 | """ |
197 | 156 | # Transform old arguments to the ns enum. |
198 | 157 | try: |
199 | alg = _args_to_enum(**_kwargs) | alg | |
158 | alg = utils.args_to_enum(**_kwargs) | alg | |
200 | 159 | except TypeError: |
201 | 160 | 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))) | |
203 | 162 | |
204 | 163 | # Add the _DUMB option if the locale library is broken. |
205 | 164 | if alg & ns.LOCALEALPHA and natsort.compat.locale.dumb_sort(): |
206 | alg |= ns._DUMB | |
165 | alg |= ns_DUMB | |
207 | 166 | |
208 | 167 | # Set some variables that will be passed to the factory functions |
209 | 168 | if alg & ns.NUMAFTER: |
218 | 177 | else: |
219 | 178 | sep = natsort.compat.locale.null_string |
220 | 179 | pre_sep = natsort.compat.locale.null_string |
221 | regex = _regex_chooser[alg & ns._NUMERIC_ONLY] | |
180 | regex = utils.regex_chooser(alg) | |
222 | 181 | |
223 | 182 | # 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) | |
227 | 186 | |
228 | 187 | # 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 | |
232 | 190 | ) |
233 | 191 | 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) | |
237 | 195 | |
238 | 196 | # Return the natsort key with the parsing path pre-chosen. |
239 | 197 | return partial( |
240 | _natsort_key, | |
198 | utils.natsort_key, | |
241 | 199 | key=key, |
242 | 200 | string_func=string_func, |
243 | 201 | bytes_func=bytes_func, |
244 | num_func=num_func | |
202 | num_func=num_func, | |
245 | 203 | ) |
246 | 204 | |
247 | 205 | |
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 | """ | |
251 | 224 | Sorts an iterable naturally. |
252 | 225 | |
253 | Sorts an iterable naturally (alphabetically and numerically), | |
254 | not lexicographically. Returns a list containing a sorted copy | |
255 | of the iterable. | |
256 | ||
257 | 226 | Parameters |
258 | 227 | ---------- |
259 | 228 | seq : iterable |
260 | The iterable to sort. | |
229 | The input to sort. | |
261 | 230 | |
262 | 231 | key : callable, optional |
263 | 232 | A key used to determine how to sort each element of the iterable. |
276 | 245 | Returns |
277 | 246 | ------- |
278 | 247 | out: list |
279 | The sorted sequence. | |
248 | The sorted input. | |
280 | 249 | |
281 | 250 | See Also |
282 | 251 | -------- |
294 | 263 | [{u}'num2', {u}'num3', {u}'num5'] |
295 | 264 | |
296 | 265 | """ |
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 | """ | |
304 | 273 | Identical to :func:`natsorted`. |
305 | 274 | |
306 | 275 | This function exists for backwards compatibility with `natsort` |
315 | 284 | |
316 | 285 | |
317 | 286 | @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 | """ | |
320 | 289 | Convenience function to properly sort non-numeric characters. |
321 | 290 | |
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)``. | |
325 | 292 | |
326 | 293 | Parameters |
327 | 294 | ---------- |
328 | 295 | seq : iterable |
329 | The sequence to sort. | |
296 | The input to sort. | |
330 | 297 | |
331 | 298 | key : callable, optional |
332 | 299 | A key used to determine how to sort each element of the sequence. |
345 | 312 | Returns |
346 | 313 | ------- |
347 | 314 | out : list |
348 | The sorted sequence. | |
315 | The sorted input. | |
349 | 316 | |
350 | 317 | See Also |
351 | 318 | -------- |
370 | 337 | |
371 | 338 | |
372 | 339 | @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 | """ | |
375 | 342 | Convenience function to properly sort signed floats. |
376 | 343 | |
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 | |
379 | 345 | ``natsorted(seq, alg=ns.REAL)``. |
380 | 346 | |
381 | 347 | The behavior of :func:`realsorted` for `natsort` version >= 4.0.0 |
385 | 351 | Parameters |
386 | 352 | ---------- |
387 | 353 | seq : iterable |
388 | The sequence to sort. | |
354 | The input to sort. | |
389 | 355 | |
390 | 356 | key : callable, optional |
391 | 357 | A key used to determine how to sort each element of the sequence. |
404 | 370 | Returns |
405 | 371 | ------- |
406 | 372 | out : list |
407 | The sorted sequence. | |
373 | The sorted input. | |
408 | 374 | |
409 | 375 | See Also |
410 | 376 | -------- |
425 | 391 | |
426 | 392 | |
427 | 393 | @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. | |
431 | 397 | |
432 | 398 | 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. | |
436 | 402 | |
437 | 403 | Parameters |
438 | 404 | ---------- |
439 | 405 | seq : iterable |
440 | The sequence to sort. | |
406 | The input to sort. | |
441 | 407 | |
442 | 408 | key : callable, optional |
443 | 409 | A key used to determine how to sort each element of the sequence. |
456 | 422 | Returns |
457 | 423 | ------- |
458 | 424 | out : tuple |
459 | The ordered indexes of the sequence. | |
425 | The ordered indexes of the input. | |
460 | 426 | |
461 | 427 | See Also |
462 | 428 | -------- |
484 | 450 | if key is None: |
485 | 451 | newkey = itemgetter(1) |
486 | 452 | else: |
453 | ||
487 | 454 | def newkey(x): |
488 | 455 | return key(itemgetter(1)(x)) |
456 | ||
489 | 457 | # Pair the index and sequence together, then sort by element |
490 | 458 | 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)) | |
493 | 460 | return [x for x, _ in index_seq_pair] |
494 | 461 | |
495 | 462 | |
496 | 463 | @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 | """ | |
499 | 466 | Identical to :func:`index_natsorted`. |
500 | 467 | |
501 | 468 | This function exists for backwards compatibility with |
513 | 480 | |
514 | 481 | |
515 | 482 | @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 | """ | |
526 | 485 | This is a wrapper around ``index_natsorted(seq, alg=ns.LOCALE)``. |
527 | 486 | |
528 | 487 | Parameters |
529 | 488 | ---------- |
530 | 489 | seq: iterable |
531 | The sequence to sort. | |
490 | The input to sort. | |
532 | 491 | |
533 | 492 | key: callable, optional |
534 | 493 | A key used to determine how to sort each element of the sequence. |
547 | 506 | Returns |
548 | 507 | ------- |
549 | 508 | out : tuple |
550 | The ordered indexes of the sequence. | |
509 | The ordered indexes of the input. | |
551 | 510 | |
552 | 511 | See Also |
553 | 512 | -------- |
571 | 530 | |
572 | 531 | |
573 | 532 | @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 | """ | |
584 | 535 | This is a wrapper around ``index_natsorted(seq, alg=ns.REAL)``. |
585 | 536 | |
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 | ||
590 | 537 | Parameters |
591 | 538 | ---------- |
592 | 539 | seq: iterable |
593 | The sequence to sort. | |
540 | The input to sort. | |
594 | 541 | |
595 | 542 | key: callable, optional |
596 | 543 | A key used to determine how to sort each element of the sequence. |
609 | 556 | Returns |
610 | 557 | ------- |
611 | 558 | out : tuple |
612 | The ordered indexes of the sequence. | |
559 | The ordered indexes of the input. | |
613 | 560 | |
614 | 561 | See Also |
615 | 562 | -------- |
628 | 575 | return index_natsorted(seq, key, reverse, alg | ns.REAL) |
629 | 576 | |
630 | 577 | |
578 | # noinspection PyShadowingBuiltins,PyUnresolvedReferences | |
631 | 579 | @u_format |
632 | 580 | def order_by_index(seq, index, iter=False): |
633 | """\ | |
581 | """ | |
634 | 582 | Order a given sequence by an index sequence. |
635 | 583 | |
636 | 584 | The output of `index_natsorted` is a |
690 | 638 | |
691 | 639 | if float(sys.version[:3]) < 3: |
692 | 640 | # pylint: disable=unused-variable |
693 | class natcmp(object): | |
641 | # noinspection PyUnresolvedReferences,PyPep8Naming | |
642 | class natcmp(object): # noqa: N801 | |
694 | 643 | """ |
695 | 644 | Compare two objects using a key and an algorithm. |
696 | 645 | |
725 | 674 | >>> natcmp(one, two) |
726 | 675 | -1 |
727 | 676 | """ |
677 | ||
728 | 678 | cached_keys = {} |
729 | 679 | |
730 | def __new__(cls, x, y, alg=0, *args, **kwargs): | |
680 | def __new__(cls, x, y, alg=ns.DEFAULT, *args, **kwargs): | |
731 | 681 | try: |
732 | alg = _args_to_enum(**kwargs) | alg | |
682 | alg = utils.args_to_enum(**kwargs) | alg | |
733 | 683 | 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))) | |
737 | 686 | |
738 | 687 | # Add the _DUMB option if the locale library is broken. |
739 | 688 | if alg & ns.LOCALEALPHA and natsort.compat.locale.dumb_sort(): |
740 | alg |= ns._DUMB | |
689 | alg |= ns_DUMB | |
741 | 690 | |
742 | 691 | if alg not in cls.cached_keys: |
743 | 692 | cls.cached_keys[alg] = natsort_keygen(alg=alg) |
0 | 0 | # -*- 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)): | |
11 | 82 | """ |
12 | 83 | Enum to control the `natsort` algorithm. |
13 | 84 | |
129 | 200 | True |
130 | 201 | |
131 | 202 | """ |
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 |
0 | 0 | # -*- coding: utf-8 -*- |
1 | 1 | """ |
2 | Contains all possible non-ASCII unicode numbers. | |
2 | Pre-determine the collection of unicode decimals, digits, and numerals. | |
3 | 3 | """ |
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 | |
10 | 5 | |
11 | # Std. lib imports. | |
12 | 6 | import unicodedata |
13 | 7 | |
14 | # Local imports. | |
15 | 8 | 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 | |
288 | 10 | |
289 | 11 | # Convert each hex into the literal Unicode character. |
290 | 12 | # Stop if a ValueError is raised in case of a narrow Unicode build. |
293 | 15 | numeric_chars = [] |
294 | 16 | for a in numeric_hex: |
295 | 17 | try: |
296 | l = py23_unichr(a) | |
18 | character = py23_unichr(a) | |
297 | 19 | except ValueError: # pragma: no cover |
298 | 20 | break |
299 | if unicodedata.numeric(l, None) is None: | |
21 | if unicodedata.numeric(character, None) is None: | |
300 | 22 | continue # pragma: no cover |
301 | numeric_chars.append(l) | |
23 | numeric_chars.append(character) | |
302 | 24 | |
303 | 25 | # 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] | |
306 | 27 | |
307 | 28 | # The decimal characters are a subset of the numberals |
308 | 29 | # (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] | |
311 | 31 | |
312 | 32 | # 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])) |
0 | 0 | # -*- coding: utf-8 -*- |
1 | 1 | """ |
2 | 2 | Utilities and definitions for natsort, mostly all used to define |
3 | the _natsort_key function. | |
3 | the natsort_key function. | |
4 | 4 | |
5 | 5 | SOME CONVENTIONS USED IN THIS FILE. |
6 | 6 | |
26 | 26 | |
27 | 27 | >>> def factory(parameter): |
28 | 28 | ... 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) | |
31 | 31 | ... return closure |
32 | 32 | ... |
33 | 33 | |
37 | 37 | and thus has a slightly improved performance at runtime. |
38 | 38 | |
39 | 39 | """ |
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 | ||
48 | 42 | 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 | |
53 | 43 | from collections import deque |
54 | 44 | from functools import partial, reduce |
45 | from itertools import chain as ichain | |
55 | 46 | 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 | |
56 | 51 | 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 | |
61 | 56 | 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, | |
66 | 64 | ) |
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 | ||
78 | 68 | if PY_VERSION >= 3: |
79 | 69 | long = int |
80 | 70 | |
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] | |
115 | 154 | |
116 | 155 | |
117 | 156 | def _no_op(x): |
118 | """A function that does nothing.""" | |
157 | """A function that does nothing and returns the input as-is.""" | |
119 | 158 | return x |
120 | 159 | |
121 | 160 | |
122 | 161 | 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) | |
126 | 179 | if NEWPY: |
127 | return partial(normalize, normalization_form) | |
180 | return wrapped | |
128 | 181 | 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 | """ | |
140 | 187 | Key to sort strings and numbers naturally. |
141 | 188 | |
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 | |
149 | 195 | key : callable | None |
196 | A key to apply to the *val* before any other operations are performed. | |
150 | 197 | 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. | |
151 | 201 | 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. | |
152 | 205 | 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. | |
153 | 209 | |
154 | 210 | Returns |
155 | 211 | ------- |
156 | 212 | 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 | |
158 | 225 | |
159 | 226 | """ |
160 | 227 | |
171 | 238 | if type(val) in (bytes,): |
172 | 239 | return bytes_func(val) |
173 | 240 | |
174 | # Otherwise, assume it is an iterable that must be parses recursively. | |
241 | # Otherwise, assume it is an iterable that must be parsed recursively. | |
175 | 242 | # Do not apply the key recursively. |
176 | 243 | 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 | ) | |
180 | 247 | |
181 | 248 | # If that failed, it must be a number. |
182 | 249 | except TypeError: |
183 | 250 | return num_func(val) |
184 | 251 | |
185 | 252 | |
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 | """ | |
188 | 274 | # We don't worry about ns.UNGROUPLETTERS | ns.LOCALEALPHA because |
189 | 275 | # bytes cannot be compared to strings. |
190 | 276 | if alg & ns.PATH and alg & ns.IGNORECASE: |
197 | 283 | return lambda x: (x,) |
198 | 284 | |
199 | 285 | |
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): | |
205 | 318 | """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 | |
207 | 320 | |
208 | 321 | # Return the function, possibly wrapping in tuple if PATH is selected. |
209 | 322 | if alg & ns.PATH and alg & ns.UNGROUPLETTERS and alg & ns.LOCALEALPHA: |
216 | 329 | return func |
217 | 330 | |
218 | 331 | |
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 | """ | |
224 | 379 | # Sometimes we store the "original" input before transformation, |
225 | 380 | # 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) | |
227 | 382 | original_func = input_transform if orig_after_xfrm else _no_op |
228 | 383 | normalize_input = _normalize_input_factory(alg) |
229 | 384 | |
233 | 388 | # to also be the transformation function. |
234 | 389 | x = normalize_input(x) |
235 | 390 | 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. | |
238 | 393 | 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. | |
241 | 396 | |
242 | 397 | return func |
243 | 398 | |
244 | 399 | |
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 | """ | |
256 | 444 | 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. | |
257 | 448 | types = (int, float, long) |
258 | 449 | first = next(iterable) |
259 | 450 | if type(first) in types: |
278 | 469 | return |
279 | 470 | |
280 | 471 | |
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 | ||
285 | 491 | """ |
286 | 492 | # Shortcuts. |
287 | 493 | lowfirst = alg & ns.LOWERCASEFIRST |
288 | dumb = alg & ns._DUMB | |
494 | dumb = alg & ns_DUMB | |
289 | 495 | |
290 | 496 | # Build the chain of functions to execute in order. |
291 | 497 | function_chain = [] |
292 | 498 | if (dumb and not lowfirst) or (lowfirst and not dumb): |
293 | function_chain.append(methodcaller('swapcase')) | |
499 | function_chain.append(methodcaller("swapcase")) | |
294 | 500 | |
295 | 501 | if alg & ns.IGNORECASE: |
296 | 502 | if NEWPY: |
297 | function_chain.append(methodcaller('casefold')) | |
503 | function_chain.append(methodcaller("casefold")) | |
298 | 504 | else: |
299 | function_chain.append(methodcaller('lower')) | |
505 | function_chain.append(methodcaller("lower")) | |
300 | 506 | |
301 | 507 | if alg & ns.LOCALENUM: |
302 | 508 | # Create a regular expression that will remove thousands separators. |
303 | strip_thousands = r''' | |
509 | strip_thousands = r""" | |
304 | 510 | (?<=[0-9]{{1}}) # At least 1 number |
305 | 511 | (?<![0-9]{{4}}) # No more than 3 numbers |
306 | 512 | {nodecimal} # Cannot follow decimal |
308 | 514 | (?=[0-9]{{3}} # Three numbers must follow |
309 | 515 | ([^0-9]|$) # But a non-number after that |
310 | 516 | ) |
311 | ''' | |
312 | nodecimal = r'' | |
517 | """ | |
518 | nodecimal = r"" | |
313 | 519 | if alg & ns.FLOAT: |
314 | 520 | # Make a regular expression component that will ensure no |
315 | 521 | # separators are removed after a decimal point. |
316 | 522 | 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 | ) | |
323 | 530 | 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, "")) | |
325 | 532 | |
326 | 533 | # Create a regular expression that will change the decimal point to |
327 | 534 | # a period if not already a period. |
328 | 535 | 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])" | |
331 | 538 | switch_decimal = switch_decimal.format(decimal=decimal) |
332 | 539 | switch_decimal = re.compile(switch_decimal) |
333 | function_chain.append(partial(switch_decimal.sub, '.')) | |
540 | function_chain.append(partial(switch_decimal.sub, ".")) | |
334 | 541 | |
335 | 542 | # Return the chained functions. |
336 | 543 | return chain_functions(function_chain) |
337 | 544 | |
338 | 545 | |
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 | ||
343 | 565 | """ |
344 | 566 | # Shortcuts. |
345 | 567 | use_locale = alg & ns.LOCALEALPHA |
346 | dumb = alg & ns._DUMB | |
568 | dumb = alg & ns_DUMB | |
347 | 569 | 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") | |
349 | 571 | |
350 | 572 | # Build the chain of functions to execute in order. |
351 | 573 | func_chain = [] |
352 | 574 | if group_letters: |
353 | func_chain.append(_groupletters) | |
575 | func_chain.append(groupletters) | |
354 | 576 | if use_locale: |
355 | 577 | 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 {} | |
357 | 579 | |
358 | 580 | # Return the correct chained functions. |
359 | 581 | if alg & ns.FLOAT: |
360 | kwargs['nan'] = nan_val | |
582 | # noinspection PyTypeChecker | |
583 | kwargs["nan"] = nan_val | |
361 | 584 | return partial(fast_float, **kwargs) |
362 | 585 | else: |
363 | 586 | return partial(fast_int, **kwargs) |
364 | 587 | |
365 | 588 | |
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 | ||
370 | 614 | """ |
371 | 615 | 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): | |
376 | 620 | """ |
377 | 621 | Return a tuple with the first character of the first element |
378 | 622 | of the return value as the first element, and the return value |
382 | 626 | split_val = tuple(split_val) |
383 | 627 | if not split_val: |
384 | 628 | 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 | |
387 | 631 | else: |
388 | return (transform(val[0]),), split_val | |
632 | return (_transform(val[0]),), split_val | |
633 | ||
389 | 634 | return func |
390 | 635 | else: |
391 | 636 | return lambda split_val, val: tuple(split_val) |
392 | 637 | |
393 | 638 | |
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)) | |
397 | 664 | |
398 | 665 | |
399 | 666 | def chain_functions(functions): |
410 | 677 | |
411 | 678 | Returns |
412 | 679 | ------- |
413 | A single argument function. | |
680 | func : callable | |
681 | A single argument function. | |
414 | 682 | |
415 | 683 | Examples |
416 | 684 | -------- |
432 | 700 | return partial(reduce, lambda res, f: f(res), functions) |
433 | 701 | |
434 | 702 | |
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 | """ | |
437 | 720 | try: |
438 | 721 | return s.decode(encoding) |
439 | except UnicodeError: | |
440 | raise | |
441 | 722 | except (AttributeError, TypeError): |
442 | 723 | return s |
443 | 724 | |
444 | 725 | |
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 | """ | |
448 | 750 | if has_pathlib and isinstance(s, PurePath): |
449 | 751 | s = py23_str(s) |
450 | 752 | path_parts = deque() |
486 | 788 | return ichain(path_parts, base_parts) |
487 | 789 | |
488 | 790 | |
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 | """ | |
491 | 797 | alg = 0 |
492 | keys = ('number_type', 'signed', 'exp', 'as_path', 'py3_safe') | |
798 | keys = ("number_type", "signed", "exp", "as_path", "py3_safe") | |
493 | 799 | if any(x not in keys for x in kwargs): |
494 | 800 | 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: | |
497 | 803 | msg = "The 'number_type' argument is deprecated as of 3.5.0, " |
498 | 804 | msg += "please use 'alg=ns.FLOAT', 'alg=ns.INT', or 'alg=ns.VERSION'" |
499 | 805 | 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: | |
504 | 810 | msg = "The 'signed' argument is deprecated as of 3.5.0, " |
505 | 811 | msg += "please use 'alg=ns.SIGNED'." |
506 | 812 | 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: | |
509 | 815 | msg = "The 'exp' argument is deprecated as of 3.5.0, " |
510 | 816 | msg += "please use 'alg=ns.NOEXP'." |
511 | 817 | 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: | |
514 | 820 | msg = "The 'as_path' argument is deprecated as of 3.5.0, " |
515 | 821 | msg += "please use 'alg=ns.PATH'." |
516 | 822 | warn(msg, DeprecationWarning) |
517 | alg |= (ns.PATH * kwargs['as_path']) | |
823 | alg |= ns.PATH * kwargs["as_path"] | |
518 | 824 | return alg |
0 | 0 | [bumpversion] |
1 | current_version = 5.3.3 | |
1 | current_version = 5.4.1 | |
2 | 2 | commit = True |
3 | 3 | tag = True |
4 | 4 | tag_name = {new_version} |
24 | 24 | Programming Language :: Python :: 2.6 |
25 | 25 | Programming Language :: Python :: 2.7 |
26 | 26 | Programming Language :: Python :: 3 |
27 | Programming Language :: Python :: 3.3 | |
28 | 27 | Programming Language :: Python :: 3.4 |
29 | 28 | Programming Language :: Python :: 3.5 |
30 | 29 | Programming Language :: Python :: 3.6 |
30 | Programming Language :: Python :: 3.7 | |
31 | 31 | Topic :: Scientific/Engineering :: Information Analysis |
32 | 32 | Topic :: Utilities |
33 | 33 | Topic :: Text Processing |
40 | 40 | |
41 | 41 | [bumpversion:file:setup.py] |
42 | 42 | |
43 | [bumpversion:file:natsort/_version.py] | |
43 | [bumpversion:file:natsort/__init__.py] | |
44 | 44 | |
45 | 45 | [bumpversion:file:docs/source/conf.py] |
46 | 46 | |
47 | 47 | [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} | |
66 | 50 | |
67 | 51 | [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 | |
70 | 67 |
0 | 0 | #! /usr/bin/env python |
1 | 1 | |
2 | from setuptools import setup, find_packages | |
2 | from setuptools import find_packages, setup | |
3 | 3 | setup( |
4 | 4 | name='natsort', |
5 | version='5.3.3', | |
5 | version='5.4.1', | |
6 | 6 | packages=find_packages(), |
7 | 7 | install_requires=["argparse; python_version < '2.7'"], |
8 | 8 | entry_points={'console_scripts': ['natsort = natsort.__main__:main']}, |
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 | # -*- 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) |
3 | 3 | inputs and different settings. |
4 | 4 | """ |
5 | 5 | from __future__ import print_function |
6 | ||
6 | 7 | import cProfile |
8 | import locale | |
7 | 9 | import sys |
8 | 10 | |
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") | |
14 | 20 | |
15 | 21 | # Samples to parse |
16 | 22 | 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"] | |
24 | 30 | |
25 | 31 | basic_key = natsort_keygen() |
26 | 32 | real_key = natsort_keygen(alg=ns.REAL) |
29 | 35 | |
30 | 36 | |
31 | 37 | def prof_time_to_generate(): |
32 | print('*** Generate Plain Key ***') | |
38 | print("*** Generate Plain Key ***") | |
33 | 39 | for _ in py23_range(100000): |
34 | 40 | natsort_keygen() |
35 | cProfile.run('prof_time_to_generate()', sort='time') | |
41 | ||
42 | ||
43 | cProfile.run("prof_time_to_generate()", sort="time") | |
36 | 44 | |
37 | 45 | |
38 | 46 | def prof_parsing(a, msg, key=basic_key): |
39 | 47 | print(msg) |
40 | 48 | for _ in py23_range(100000): |
41 | 49 | 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 | # -*- 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) |
5 | 5 | |
6 | 6 | import unicodedata |
7 | 7 | 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 | |
8 | 12 | 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 | ) | |
21 | 13 | |
22 | 14 | if PY_VERSION >= 3: |
23 | 15 | long = int |
67 | 59 | |
68 | 60 | |
69 | 61 | 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 | |
71 | 63 | |
72 | 64 | |
73 | 65 | 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 | |
82 | 74 | |
83 | 75 | |
84 | 76 | @given(floats(allow_nan=False)) |
87 | 79 | |
88 | 80 | |
89 | 81 | def test_fast_float_leaves_string_as_is_example(): |
90 | assert fast_float('invalid') == 'invalid' | |
82 | assert fast_float("invalid") == "invalid" | |
91 | 83 | |
92 | 84 | |
93 | 85 | @given(text().filter(not_a_float).filter(bool)) |
96 | 88 | |
97 | 89 | |
98 | 90 | 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") | |
100 | 92 | |
101 | 93 | |
102 | 94 | @given(text().filter(not_a_float).filter(bool)) |
105 | 97 | |
106 | 98 | |
107 | 99 | 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" | |
111 | 103 | |
112 | 104 | |
113 | 105 | @given(floats().filter(not_an_int)) |
116 | 108 | |
117 | 109 | |
118 | 110 | 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 | |
123 | 115 | |
124 | 116 | |
125 | 117 | @given(integers()) |
128 | 120 | |
129 | 121 | |
130 | 122 | def test_fast_int_leaves_string_as_is_example(): |
131 | assert fast_int('invalid') == 'invalid' | |
123 | assert fast_int("invalid") == "invalid" | |
132 | 124 | |
133 | 125 | |
134 | 126 | @given(text().filter(not_an_int).filter(bool)) |
137 | 129 | |
138 | 130 | |
139 | 131 | 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") | |
141 | 133 | |
142 | 134 | |
143 | 135 | @given(text().filter(not_an_int).filter(bool)) |
1 | 1 | """These test the utils.py functions.""" |
2 | 2 | from __future__ import unicode_literals |
3 | 3 | |
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 | |
6 | 7 | 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 | |
15 | 10 | |
16 | 11 | |
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 | |
19 | 21 | |
20 | 22 | |
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 | |
23 | 48 | |
24 | 49 | |
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((), "") == ((), ()) |
2 | 2 | from __future__ import unicode_literals |
3 | 3 | |
4 | 4 | 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 | |
9 | 7 | 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 | |
22 | 10 | |
23 | 11 | |
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() | |
26 | 18 | |
27 | 19 | |
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 | |
31 | 29 | |
32 | 30 | |
33 | 31 | @given(text()) |
34 | 32 | 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 | |
36 | 35 | |
37 | 36 | |
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) | |
44 | 51 | |
45 | 52 | |
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 | |
52 | 60 | |
61 | input_string_transform_func = input_string_transform_factory(ns.LOCALE) | |
62 | assert input_string_transform_func(thousands_int_str) == int_str | |
53 | 63 | |
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 | |
149 | 67 | |
150 | 68 | |
151 | 69 | # These might be too much to test with hypothesis. |
152 | 70 | |
153 | 71 | |
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 | |
163 | 84 | |
164 | 85 | |
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 | |
174 | 98 | |
175 | 99 | |
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 |
2 | 2 | Test the natsort command-line tool functions. |
3 | 3 | """ |
4 | 4 | from __future__ import print_function, unicode_literals |
5 | ||
5 | 6 | import re |
6 | 7 | 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 | |
18 | 12 | from natsort.__main__ import ( |
13 | check_filters, | |
14 | keep_entry_range, | |
15 | keep_entry_value, | |
19 | 16 | main, |
20 | 17 | range_check, |
21 | check_filter, | |
22 | keep_entry_range, | |
23 | exclude_entry, | |
24 | 18 | sort_and_print_entries, |
25 | 19 | ) |
26 | 20 | |
27 | 21 | |
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 | |
63 | 58 | |
64 | 59 | |
65 | 60 | class Args: |
66 | 61 | """A dummy class to simulate the argparse Namespace object""" |
62 | ||
67 | 63 | def __init__(self, filt, reverse_filter, exclude, as_path, reverse): |
68 | 64 | self.filter = filt |
69 | 65 | self.reverse_filter = reverse_filter |
70 | 66 | self.exclude = exclude |
71 | 67 | self.reverse = reverse |
72 | self.number_type = 'float' | |
68 | self.number_type = "float" | |
73 | 69 | self.signed = True |
74 | 70 | self.exp = True |
75 | 71 | self.paths = as_path |
76 | 72 | self.locale = 0 |
77 | 73 | |
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) | |
162 | 143 | |
163 | 144 | |
164 | 145 | # Each test has an "example" version for demonstrative purposes, |
165 | 146 | # and a test that uses the hypothesis module. |
166 | 147 | |
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(): | |
168 | 150 | assert range_check(10, 11) == (10.0, 11.0) |
169 | 151 | assert range_check(6.4, 30) == (6.4, 30.0) |
170 | 152 | |
171 | 153 | |
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): | |
174 | 156 | # 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)) | |
176 | 161 | assert range_check(x, y) == (x, y) |
177 | 162 | |
178 | 163 | |
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"): | |
188 | 166 | 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): | |
194 | 171 | # 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"): | |
197 | 174 | 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 |
3 | 3 | |
4 | 4 | Note that these tests are only relevant for Python version < 3. |
5 | 5 | """ |
6 | import sys | |
7 | 6 | from functools import partial |
8 | from compat.mock import patch | |
9 | 7 | |
10 | 8 | import pytest |
11 | 9 | from hypothesis import given |
12 | 10 | from hypothesis.strategies import floats, integers, lists |
13 | ||
14 | 11 | 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 | |
19 | 13 | |
20 | 14 | if PY_VERSION < 3: |
21 | 15 | from natsort import natcmp |
23 | 17 | |
24 | 18 | class Comparable(object): |
25 | 19 | """Stub class for testing natcmp functionality.""" |
20 | ||
26 | 21 | def __init__(self, value): |
27 | 22 | self.value = value |
28 | 23 | |
30 | 25 | return natcmp(self.value, other.value) |
31 | 26 | |
32 | 27 | |
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: | |
39 | 30 | |
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 | |
41 | 37 | |
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 | |
42 | 45 | |
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 | |
51 | 51 | |
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 | |
57 | 57 | |
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") | |
63 | 61 | |
62 | def test_classes_can_utilize_max_or_min(self): | |
63 | comparables = [Comparable(i) for i in range(10)] | |
64 | 64 | |
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] | |
70 | 67 | |
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) | |
73 | 71 | |
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) | |
76 | 75 | |
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)) | |
77 | 81 | |
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] |
2 | 2 | from __future__ import unicode_literals |
3 | 3 | |
4 | 4 | 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 | |
28 | 9 | |
29 | 10 | if PY_VERSION >= 3: |
30 | 11 | long = int |
31 | 12 | |
32 | 13 | |
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!") | |
40 | 19 | |
41 | 20 | |
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!") | |
71 | 23 | |
72 | 24 | |
73 | 25 | @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 | |
76 | 28 | |
77 | 29 | |
78 | @pytest.mark.skipif(PY_VERSION < 3, reason='only valid on python3') | |
30 | @pytest.mark.skipif(PY_VERSION < 3, reason="only valid on python3") | |
79 | 31 | @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 | |
82 | 34 | |
83 | 35 | |
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 | |
88 | 39 | |
89 | 40 | |
90 | 41 | @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) | |
93 | 44 | |
94 | 45 | |
95 | 46 | @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) |
2 | 2 | Here are a collection of examples of how this module can be used. |
3 | 3 | See the README or the natsort homepage for more details. |
4 | 4 | """ |
5 | from __future__ import unicode_literals, print_function | |
5 | from __future__ import print_function, unicode_literals | |
6 | 6 | |
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 | |
16 | 10 | 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] | |
25 | 11 | |
26 | 12 | |
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] | |
40 | 16 | |
41 | 17 | |
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" | |
47 | 21 | |
48 | 22 | |
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) | |
53 | 29 | |
54 | 30 | |
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) | |
60 | 33 | |
61 | 34 | |
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") | |
65 | 39 | |
66 | 40 | |
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 | |
70 | 48 | |
71 | 49 | |
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 | |
75 | 83 | |
76 | 84 | |
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 | |
86 | 99 | |
87 | 100 | |
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. | |
90 | 136 | 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 | |
95 | 155 | |
96 | 156 | |
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 |
2 | 2 | Here are a collection of examples of how this module can be used. |
3 | 3 | See the README or the natsort homepage for more details. |
4 | 4 | """ |
5 | from __future__ import unicode_literals, print_function | |
5 | from __future__ import print_function, unicode_literals | |
6 | ||
7 | from operator import itemgetter | |
8 | ||
6 | 9 | import pytest |
7 | import locale | |
10 | from natsort import as_utf8, natsorted, ns | |
8 | 11 | from natsort.compat.py23 import PY_VERSION |
9 | from operator import itemgetter | |
10 | 12 | 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( | |
34 | 42 | # 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] | |
92 | 99 | # 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"): | |
113 | 115 | 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 | |
120 | 122 | |
121 | 123 | |
122 | 124 | 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(): | |
155 | 161 | # 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 | |
282 | 251 | |
283 | 252 | |
284 | 253 | 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 |
2 | 2 | Here are a collection of examples of how this module can be used. |
3 | 3 | See the README or the natsort homepage for more details. |
4 | 4 | """ |
5 | from __future__ import unicode_literals, print_function | |
5 | from __future__ import print_function, unicode_literals | |
6 | ||
6 | 7 | from operator import itemgetter |
7 | from natsort.compat.py23 import PY_VERSION | |
8 | ||
9 | import pytest | |
8 | 10 | from natsort import ( |
9 | natsorted, | |
10 | index_natsorted, | |
11 | versorted, | |
12 | index_versorted, | |
11 | as_ascii, | |
12 | as_utf8, | |
13 | decoder, | |
13 | 14 | humansorted, |
14 | 15 | index_humansorted, |
16 | index_natsorted, | |
17 | index_realsorted, | |
18 | index_versorted, | |
19 | natsorted, | |
20 | ns, | |
21 | order_by_index, | |
15 | 22 | realsorted, |
16 | index_realsorted, | |
17 | order_by_index, | |
18 | ns, | |
19 | decoder, | |
20 | as_ascii, | |
21 | as_utf8, | |
23 | versorted, | |
22 | 24 | ) |
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"] | |
23 | 41 | |
24 | 42 | |
25 | 43 | 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 | |
31 | 49 | 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 | |
33 | 53 | 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 | |
36 | 58 | |
37 | 59 | |
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") | |
40 | 62 | |
41 | 63 | |
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") | |
44 | 66 | |
45 | 67 | |
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): | |
48 | 69 | # versorted is retained for backwards compatibility |
49 | assert versorted(a) == natsorted(a) | |
70 | assert versorted(version_list) == natsorted(version_list) | |
50 | 71 | |
51 | 72 | |
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) | |
55 | 75 | |
56 | 76 | |
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) | |
60 | 80 | |
61 | 81 | |
62 | 82 | 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) | |
66 | 86 | 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"] | |
69 | 89 | |
70 | 90 | |
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] | |
74 | 94 | |
75 | 95 | |
76 | 96 | 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 | |
79 | 100 | |
80 | 101 | |
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) | |
84 | 105 | |
85 | 106 | |
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) | |
89 | 109 | |
90 | 110 | |
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) | |
112 | 114 | |
113 | 115 | |
114 | 116 | def test_order_by_index_sorts_list_according_to_order_of_integer_list(): |
115 | a = ['num3', 'num5', 'num2'] | |
117 | given = ["num3", "num5", "num2"] | |
116 | 118 | 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 | |
119 | 122 | |
120 | 123 | |
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"] | |
123 | 126 | 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] |
1 | 1 | """These test the utils.py functions.""" |
2 | 2 | from __future__ import unicode_literals |
3 | 3 | |
4 | from natsort.ns_enum import ns | |
5 | from natsort.utils import _parse_bytes_factory | |
4 | import pytest | |
6 | 5 | from hypothesis import given |
7 | 6 | from hypothesis.strategies import binary |
7 | from natsort.ns_enum import ns | |
8 | from natsort.utils import parse_bytes_factory | |
8 | 9 | |
9 | 10 | |
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) |
1 | 1 | """These test the utils.py functions.""" |
2 | 2 | from __future__ import unicode_literals |
3 | 3 | |
4 | import pytest | |
5 | from hypothesis import given | |
6 | from hypothesis.strategies import floats, integers | |
4 | 7 | 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 | |
13 | 9 | |
14 | 10 | |
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) | |
17 | 25 | |
18 | 26 | |
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 |
1 | 1 | """These test the utils.py functions.""" |
2 | 2 | from __future__ import unicode_literals |
3 | 3 | |
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 | |
35 | 5 | |
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 | |
38 | 14 | |
39 | 15 | |
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 | |
49 | 20 | |
50 | 21 | |
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 | |
54 | 28 | |
55 | 29 | |
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 | |
59 | 35 | |
60 | 36 | |
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 | ) | |
63 | 49 | |
64 | 50 | |
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) | |
68 | 56 | |
69 | 57 | |
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 | |
74 | 86 | |
87 | # The result is in our CustomTuple. | |
88 | assert isinstance(result, CustomTuple) | |
75 | 89 | |
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 |
1 | 1 | """These test the utils.py functions.""" |
2 | 2 | from __future__ import unicode_literals |
3 | 3 | |
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 | |
10 | 10 | 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) | |
24 | 30 | |
25 | 31 | |
26 | 32 | 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 | |
28 | 35 | |
29 | 36 | |
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) | |
91 | 73 | 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)) | |
93 | 75 | 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): | |
95 | 77 | 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 |
2 | 2 | Test the Unicode numbers module. |
3 | 3 | """ |
4 | 4 | from __future__ import unicode_literals |
5 | ||
5 | 6 | import unicodedata |
7 | ||
6 | 8 | from natsort.compat.py23 import py23_range, py23_unichr |
7 | 9 | from natsort.unicode_numbers import ( |
8 | numeric_hex, | |
9 | numeric_chars, | |
10 | numeric, | |
10 | decimal_chars, | |
11 | decimals, | |
11 | 12 | digit_chars, |
12 | 13 | digits, |
13 | decimal_chars, | |
14 | decimals, | |
15 | 14 | digits_no_decimals, |
15 | numeric, | |
16 | numeric_chars, | |
17 | numeric_hex, | |
16 | 18 | numeric_no_decimals, |
17 | 19 | ) |
18 | 20 | |
42 | 44 | a = py23_unichr(i) |
43 | 45 | except ValueError: |
44 | 46 | break |
45 | if a in set('0123456789'): | |
47 | if a in set("0123456789"): | |
46 | 48 | continue |
47 | 49 | if unicodedata.numeric(a, None) is not None: |
48 | 50 | assert i in set_numeric_hex |
62 | 64 | |
63 | 65 | |
64 | 66 | 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) |
5 | 5 | import string |
6 | 6 | from itertools import chain |
7 | 7 | 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 | |
9 | 14 | 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 | ) | |
41 | 15 | |
42 | 16 | |
43 | 17 | 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") | |
47 | 21 | |
48 | 22 | |
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}) | |
52 | 26 | |
53 | 27 | |
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 | |
59 | 45 | |
60 | 46 | |
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 | |
66 | 62 | |
67 | 63 | |
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 | |
157 | 103 | |
158 | 104 | |
159 | 105 | def test_chain_functions_is_a_no_op_if_no_functions_are_given(): |
160 | 106 | x = 2345 |
161 | assert chain_functions([])(x) is x | |
107 | assert utils.chain_functions([])(x) is x | |
162 | 108 | |
163 | 109 | |
164 | 110 | 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 | |
167 | 113 | |
168 | 114 | |
169 | 115 | def test_chain_functions_combines_functions_in_given_order(): |
170 | 116 | 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)) | |
172 | 118 | |
173 | 119 | |
174 | 120 | # Each test has an "example" version for demonstrative purposes, |
175 | 121 | # and a test that uses the hypothesis module. |
176 | 122 | |
123 | ||
177 | 124 | 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" | |
180 | 127 | |
181 | 128 | |
182 | 129 | @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 | ) | |
185 | 134 | |
186 | 135 | |
187 | 136 | 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"] | |
190 | 139 | |
191 | 140 | |
192 | 141 | 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] | |
194 | 143 | |
195 | 144 | |
196 | 145 | 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] | |
199 | 147 | |
200 | 148 | |
201 | @given(lists(elements=text().filter(bool) | integers())) | |
149 | @given(lists(elements=text().filter(bool) | integers(), min_size=3)) | |
202 | 150 | 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) | |
204 | 158 | |
205 | 159 | |
206 | 160 | 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) | |
211 | 165 | |
212 | 166 | |
213 | 167 | @given(lists(sampled_from(string.ascii_letters), min_size=2).filter(all)) |
214 | 168 | def test_path_splitter_splits_path_string_by_separator(x): |
215 | 169 | 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) | |
217 | 171 | |
218 | 172 | |
219 | 173 | 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" | |
221 | 175 | 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 | ) | |
223 | 180 | |
224 | 181 | |
225 | 182 | @given(lists(sampled_from(string.ascii_letters), min_size=3).filter(all)) |
226 | 183 | 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] | |
228 | 185 | 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 | ) | |
230 | 190 | |
231 | 191 | |
232 | 192 | @given(integers()) |
4 | 4 | |
5 | 5 | [tox] |
6 | 6 | envlist = |
7 | py27, py34, py35, py36, py37, pypy | |
7 | flake8, py27, py34, py35, py36, py37, pypy | |
8 | 8 | # Other valid evironments are: |
9 | 9 | # docs |
10 | 10 | # release |
28 | 28 | pytest README.rst docs/source/intro.rst docs/source/examples.rst |
29 | 29 | pytest --doctest-modules {envsitepackagesdir}/natsort |
30 | 30 | # 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 | |
32 | 41 | |
33 | 42 | # Build documentation. |
34 | 43 | [testenv:docs] |
38 | 47 | commands = |
39 | 48 | {envpython} setup.py build_sphinx |
40 | 49 | |
50 | # Release the code to PyPI | |
41 | 51 | [testenv:release] |
42 | 52 | deps = |
43 | 53 | twine |