nameparser 1.4.0__tar.gz → 2.0.0rc1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. nameparser-2.0.0rc1/PKG-INFO +106 -0
  2. nameparser-2.0.0rc1/README.rst +80 -0
  3. nameparser-2.0.0rc1/nameparser/__init__.py +40 -0
  4. nameparser-2.0.0rc1/nameparser/__main__.py +48 -0
  5. nameparser-2.0.0rc1/nameparser/_config_shim.py +1125 -0
  6. nameparser-2.0.0rc1/nameparser/_facade.py +701 -0
  7. nameparser-2.0.0rc1/nameparser/_lexicon.py +534 -0
  8. nameparser-2.0.0rc1/nameparser/_locale.py +81 -0
  9. nameparser-2.0.0rc1/nameparser/_parser.py +130 -0
  10. nameparser-2.0.0rc1/nameparser/_pipeline/__init__.py +37 -0
  11. nameparser-2.0.0rc1/nameparser/_pipeline/_assemble.py +66 -0
  12. nameparser-2.0.0rc1/nameparser/_pipeline/_assign.py +276 -0
  13. nameparser-2.0.0rc1/nameparser/_pipeline/_classify.py +95 -0
  14. nameparser-2.0.0rc1/nameparser/_pipeline/_extract.py +241 -0
  15. nameparser-2.0.0rc1/nameparser/_pipeline/_group.py +341 -0
  16. nameparser-2.0.0rc1/nameparser/_pipeline/_post_rules.py +129 -0
  17. nameparser-2.0.0rc1/nameparser/_pipeline/_segment.py +122 -0
  18. nameparser-2.0.0rc1/nameparser/_pipeline/_state.py +94 -0
  19. nameparser-2.0.0rc1/nameparser/_pipeline/_tokenize.py +125 -0
  20. nameparser-2.0.0rc1/nameparser/_pipeline/_vocab.py +126 -0
  21. nameparser-2.0.0rc1/nameparser/_policy.py +456 -0
  22. nameparser-2.0.0rc1/nameparser/_render.py +165 -0
  23. nameparser-2.0.0rc1/nameparser/_types.py +606 -0
  24. nameparser-2.0.0rc1/nameparser/_version.py +9 -0
  25. nameparser-2.0.0rc1/nameparser/config/__init__.py +20 -0
  26. nameparser-2.0.0rc1/nameparser/config/_invariants.py +27 -0
  27. nameparser-2.0.0rc1/nameparser/config/bound_first_names.py +28 -0
  28. nameparser-2.0.0rc1/nameparser/config/capitalization.py +16 -0
  29. nameparser-2.0.0rc1/nameparser/config/conjunctions.py +33 -0
  30. nameparser-2.0.0rc1/nameparser/config/maiden_markers.py +42 -0
  31. {nameparser-1.4.0 → nameparser-2.0.0rc1}/nameparser/config/prefixes.py +33 -0
  32. {nameparser-1.4.0 → nameparser-2.0.0rc1}/nameparser/config/regexes.py +8 -6
  33. {nameparser-1.4.0 → nameparser-2.0.0rc1}/nameparser/config/suffixes.py +50 -3
  34. {nameparser-1.4.0 → nameparser-2.0.0rc1}/nameparser/config/titles.py +102 -2
  35. nameparser-2.0.0rc1/nameparser/locales/__init__.py +62 -0
  36. nameparser-2.0.0rc1/nameparser/locales/ru.py +59 -0
  37. nameparser-2.0.0rc1/nameparser/locales/tr_az.py +49 -0
  38. nameparser-2.0.0rc1/nameparser/parser.py +6 -0
  39. nameparser-2.0.0rc1/nameparser/util.py +13 -0
  40. nameparser-2.0.0rc1/nameparser.egg-info/PKG-INFO +106 -0
  41. nameparser-2.0.0rc1/nameparser.egg-info/SOURCES.txt +100 -0
  42. {nameparser-1.4.0 → nameparser-2.0.0rc1}/pyproject.toml +23 -5
  43. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/base.py +8 -2
  44. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/conftest.py +27 -28
  45. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/test_bound_first_names.py +3 -8
  46. nameparser-2.0.0rc1/tests/test_comma_variants.py +34 -0
  47. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/test_constants.py +301 -509
  48. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/test_east_slavic_patronymic_order.py +21 -18
  49. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/test_first_name.py +4 -4
  50. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/test_initials.py +35 -48
  51. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/test_nicknames.py +27 -59
  52. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/test_output_format.py +39 -43
  53. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/test_prefixes.py +5 -3
  54. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/test_python_api.py +87 -163
  55. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/test_suffixes.py +13 -6
  56. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/test_titles.py +5 -1
  57. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/test_turkic_patronymic_order.py +23 -15
  58. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/test_variations.py +6 -8
  59. nameparser-2.0.0rc1/tests/v2/__init__.py +0 -0
  60. nameparser-2.0.0rc1/tests/v2/cases.py +469 -0
  61. nameparser-2.0.0rc1/tests/v2/conftest.py +41 -0
  62. nameparser-2.0.0rc1/tests/v2/pipeline/__init__.py +0 -0
  63. nameparser-2.0.0rc1/tests/v2/pipeline/test_assemble.py +130 -0
  64. nameparser-2.0.0rc1/tests/v2/pipeline/test_assign.py +171 -0
  65. nameparser-2.0.0rc1/tests/v2/pipeline/test_classify.py +78 -0
  66. nameparser-2.0.0rc1/tests/v2/pipeline/test_extract.py +259 -0
  67. nameparser-2.0.0rc1/tests/v2/pipeline/test_group.py +161 -0
  68. nameparser-2.0.0rc1/tests/v2/pipeline/test_post_rules.py +122 -0
  69. nameparser-2.0.0rc1/tests/v2/pipeline/test_segment.py +108 -0
  70. nameparser-2.0.0rc1/tests/v2/pipeline/test_state.py +110 -0
  71. nameparser-2.0.0rc1/tests/v2/pipeline/test_tokenize.py +91 -0
  72. nameparser-2.0.0rc1/tests/v2/pipeline/test_vocab.py +40 -0
  73. nameparser-2.0.0rc1/tests/v2/test_benchmark.py +122 -0
  74. nameparser-2.0.0rc1/tests/v2/test_cases.py +26 -0
  75. nameparser-2.0.0rc1/tests/v2/test_cli.py +62 -0
  76. nameparser-2.0.0rc1/tests/v2/test_config_shim.py +777 -0
  77. nameparser-2.0.0rc1/tests/v2/test_contracts.py +84 -0
  78. nameparser-2.0.0rc1/tests/v2/test_facade.py +516 -0
  79. nameparser-2.0.0rc1/tests/v2/test_facade_cases.py +75 -0
  80. nameparser-2.0.0rc1/tests/v2/test_layering.py +224 -0
  81. nameparser-2.0.0rc1/tests/v2/test_lexicon.py +465 -0
  82. nameparser-2.0.0rc1/tests/v2/test_locale.py +76 -0
  83. nameparser-2.0.0rc1/tests/v2/test_locales.py +522 -0
  84. nameparser-2.0.0rc1/tests/v2/test_parser.py +317 -0
  85. nameparser-2.0.0rc1/tests/v2/test_policy.py +408 -0
  86. nameparser-2.0.0rc1/tests/v2/test_properties.py +342 -0
  87. nameparser-2.0.0rc1/tests/v2/test_regex_sync.py +151 -0
  88. nameparser-2.0.0rc1/tests/v2/test_render.py +290 -0
  89. nameparser-2.0.0rc1/tests/v2/test_reprs.py +68 -0
  90. nameparser-2.0.0rc1/tests/v2/test_types.py +388 -0
  91. nameparser-1.4.0/PKG-INFO +0 -206
  92. nameparser-1.4.0/README.rst +0 -178
  93. nameparser-1.4.0/nameparser/__init__.py +0 -7
  94. nameparser-1.4.0/nameparser/__main__.py +0 -31
  95. nameparser-1.4.0/nameparser/_version.py +0 -2
  96. nameparser-1.4.0/nameparser/config/__init__.py +0 -996
  97. nameparser-1.4.0/nameparser/config/bound_first_names.py +0 -12
  98. nameparser-1.4.0/nameparser/config/capitalization.py +0 -10
  99. nameparser-1.4.0/nameparser/config/conjunctions.py +0 -15
  100. nameparser-1.4.0/nameparser/parser.py +0 -1654
  101. nameparser-1.4.0/nameparser/util.py +0 -16
  102. nameparser-1.4.0/nameparser.egg-info/PKG-INFO +0 -206
  103. nameparser-1.4.0/nameparser.egg-info/SOURCES.txt +0 -47
  104. nameparser-1.4.0/nameparser.egg-info/requires.txt +0 -3
  105. nameparser-1.4.0/tests/test_comma_variants.py +0 -53
  106. nameparser-1.4.0/tests/test_config_attribute_docstrings.py +0 -79
  107. nameparser-1.4.0/tests/test_parser_util.py +0 -39
  108. {nameparser-1.4.0 → nameparser-2.0.0rc1}/AUTHORS +0 -0
  109. {nameparser-1.4.0 → nameparser-2.0.0rc1}/LICENSE +0 -0
  110. {nameparser-1.4.0 → nameparser-2.0.0rc1}/MANIFEST.in +0 -0
  111. {nameparser-1.4.0 → nameparser-2.0.0rc1}/nameparser/py.typed +0 -0
  112. {nameparser-1.4.0 → nameparser-2.0.0rc1}/nameparser.egg-info/dependency_links.txt +0 -0
  113. {nameparser-1.4.0 → nameparser-2.0.0rc1}/nameparser.egg-info/top_level.txt +0 -0
  114. {nameparser-1.4.0 → nameparser-2.0.0rc1}/setup.cfg +0 -0
  115. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/__init__.py +0 -0
  116. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/test_brute_force.py +0 -0
  117. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/test_capitalization.py +0 -0
  118. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/test_conjunctions.py +0 -0
  119. {nameparser-1.4.0 → nameparser-2.0.0rc1}/tests/test_middle_name_as_last.py +0 -0
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: nameparser
3
+ Version: 2.0.0rc1
4
+ Summary: A simple Python module for parsing human names into their individual components.
5
+ Author-email: Derek Gulbranson <derek73@gmail.com>
6
+ License: LGPL
7
+ Keywords: names,parser
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Development Status :: 5 - Production/Stable
18
+ Classifier: Natural Language :: English
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: Text Processing :: Linguistic
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/x-rst
23
+ License-File: LICENSE
24
+ License-File: AUTHORS
25
+ Dynamic: license-file
26
+
27
+ Name Parser
28
+ ===========
29
+
30
+ |Build Status| |PyPI| |PyPI version| |Documentation| |License| |Downloads| |Codecov|
31
+
32
+ nameparser parses human names into seven fields — title, given, middle,
33
+ family, suffix, nickname, maiden. Results are immutable, configuration is
34
+ composable, and locale packs are opt-in.
35
+
36
+ Installation
37
+ ------------
38
+
39
+ ::
40
+
41
+ pip install nameparser
42
+
43
+ Requires Python 3.11+.
44
+
45
+ Quick Start Example
46
+ --------------------
47
+
48
+ .. code-block:: python
49
+
50
+ >>> from nameparser import parse
51
+ >>> name = parse("Dr. Juan Q. Xavier de la Vega III")
52
+ >>> name.given, name.family
53
+ ('Juan', 'de la Vega')
54
+ >>> name.render("{family}, {given}")
55
+ 'de la Vega, Juan'
56
+
57
+ Those seven fields are ``title``, ``given``, ``middle``, ``family``,
58
+ ``suffix``, ``nickname``, and ``maiden`` — plus aggregate views like
59
+ ``given_names``, ``surnames``, ``family_base``, and ``family_particles``
60
+ for combining or splitting them further.
61
+
62
+ Learn more
63
+ ----------
64
+
65
+ * `Using the parser <https://nameparser.readthedocs.io/en/latest/usage.html>`__ — the full tour: input shapes, aggregates, rendering, comparison, ambiguities, tokens
66
+ * `Customizing the parser <https://nameparser.readthedocs.io/en/latest/customize.html>`__ — vocabulary, behavior, and presentation
67
+ * `Locale packs <https://nameparser.readthedocs.io/en/latest/locales.html>`__ — opt-in bundles for East Slavic patronymics, Turkic markers, and more
68
+ * There's also a CLI: ``python -m nameparser --json "Doe, John"``
69
+
70
+ Coming from 1.x
71
+ ----------------
72
+
73
+ ``HumanName`` and ``CONSTANTS`` keep working in 2.0 — same imports, same
74
+ attributes, same mutation API. What 2.0 removes is the batch of
75
+ deprecations 1.3 and 1.4 announced, so if your test suite runs clean on
76
+ 1.4 under ``python -W error::DeprecationWarning``, you are nearly done.
77
+ Two things that check will not catch: four removals 1.4 never warned
78
+ about (three raise on contact, the fourth only warns), and one that
79
+ changes results silently — ``name == "John Smith"`` is now ``False``.
80
+ `Migrating from HumanName <https://nameparser.readthedocs.io/en/latest/migrate.html>`__
81
+ covers both, and translates a v1 customization into the new API whenever
82
+ that's convenient for you.
83
+
84
+ See the `release log <https://nameparser.readthedocs.io/en/latest/release_log.html>`__
85
+ for the full list of changes in the 2.0 series.
86
+
87
+ License
88
+ -------
89
+
90
+ LGPL licensed. See `LICENSE <https://github.com/derek73/python-nameparser/blob/master/LICENSE>`__
91
+ for details.
92
+
93
+ .. |Build Status| image:: https://github.com/derek73/python-nameparser/actions/workflows/python-package.yml/badge.svg
94
+ :target: https://github.com/derek73/python-nameparser/actions/workflows/python-package.yml
95
+ .. |PyPI| image:: https://img.shields.io/pypi/v/nameparser.svg
96
+ :target: https://pypi.org/project/nameparser/
97
+ .. |Documentation| image:: https://readthedocs.org/projects/nameparser/badge/?version=latest
98
+ :target: http://nameparser.readthedocs.io/en/latest/?badge=latest
99
+ .. |PyPI version| image:: https://img.shields.io/pypi/pyversions/nameparser.svg
100
+ :target: https://pypi.org/project/nameparser/
101
+ .. |License| image:: https://img.shields.io/pypi/l/nameparser.svg
102
+ :target: https://pypi.org/project/nameparser/
103
+ .. |Downloads| image:: https://static.pepy.tech/badge/nameparser
104
+ :target: https://pepy.tech/project/nameparser
105
+ .. |Codecov| image:: https://codecov.io/gh/derek73/python-nameparser/branch/master/graph/badge.svg
106
+ :target: https://codecov.io/gh/derek73/python-nameparser
@@ -0,0 +1,80 @@
1
+ Name Parser
2
+ ===========
3
+
4
+ |Build Status| |PyPI| |PyPI version| |Documentation| |License| |Downloads| |Codecov|
5
+
6
+ nameparser parses human names into seven fields — title, given, middle,
7
+ family, suffix, nickname, maiden. Results are immutable, configuration is
8
+ composable, and locale packs are opt-in.
9
+
10
+ Installation
11
+ ------------
12
+
13
+ ::
14
+
15
+ pip install nameparser
16
+
17
+ Requires Python 3.11+.
18
+
19
+ Quick Start Example
20
+ --------------------
21
+
22
+ .. code-block:: python
23
+
24
+ >>> from nameparser import parse
25
+ >>> name = parse("Dr. Juan Q. Xavier de la Vega III")
26
+ >>> name.given, name.family
27
+ ('Juan', 'de la Vega')
28
+ >>> name.render("{family}, {given}")
29
+ 'de la Vega, Juan'
30
+
31
+ Those seven fields are ``title``, ``given``, ``middle``, ``family``,
32
+ ``suffix``, ``nickname``, and ``maiden`` — plus aggregate views like
33
+ ``given_names``, ``surnames``, ``family_base``, and ``family_particles``
34
+ for combining or splitting them further.
35
+
36
+ Learn more
37
+ ----------
38
+
39
+ * `Using the parser <https://nameparser.readthedocs.io/en/latest/usage.html>`__ — the full tour: input shapes, aggregates, rendering, comparison, ambiguities, tokens
40
+ * `Customizing the parser <https://nameparser.readthedocs.io/en/latest/customize.html>`__ — vocabulary, behavior, and presentation
41
+ * `Locale packs <https://nameparser.readthedocs.io/en/latest/locales.html>`__ — opt-in bundles for East Slavic patronymics, Turkic markers, and more
42
+ * There's also a CLI: ``python -m nameparser --json "Doe, John"``
43
+
44
+ Coming from 1.x
45
+ ----------------
46
+
47
+ ``HumanName`` and ``CONSTANTS`` keep working in 2.0 — same imports, same
48
+ attributes, same mutation API. What 2.0 removes is the batch of
49
+ deprecations 1.3 and 1.4 announced, so if your test suite runs clean on
50
+ 1.4 under ``python -W error::DeprecationWarning``, you are nearly done.
51
+ Two things that check will not catch: four removals 1.4 never warned
52
+ about (three raise on contact, the fourth only warns), and one that
53
+ changes results silently — ``name == "John Smith"`` is now ``False``.
54
+ `Migrating from HumanName <https://nameparser.readthedocs.io/en/latest/migrate.html>`__
55
+ covers both, and translates a v1 customization into the new API whenever
56
+ that's convenient for you.
57
+
58
+ See the `release log <https://nameparser.readthedocs.io/en/latest/release_log.html>`__
59
+ for the full list of changes in the 2.0 series.
60
+
61
+ License
62
+ -------
63
+
64
+ LGPL licensed. See `LICENSE <https://github.com/derek73/python-nameparser/blob/master/LICENSE>`__
65
+ for details.
66
+
67
+ .. |Build Status| image:: https://github.com/derek73/python-nameparser/actions/workflows/python-package.yml/badge.svg
68
+ :target: https://github.com/derek73/python-nameparser/actions/workflows/python-package.yml
69
+ .. |PyPI| image:: https://img.shields.io/pypi/v/nameparser.svg
70
+ :target: https://pypi.org/project/nameparser/
71
+ .. |Documentation| image:: https://readthedocs.org/projects/nameparser/badge/?version=latest
72
+ :target: http://nameparser.readthedocs.io/en/latest/?badge=latest
73
+ .. |PyPI version| image:: https://img.shields.io/pypi/pyversions/nameparser.svg
74
+ :target: https://pypi.org/project/nameparser/
75
+ .. |License| image:: https://img.shields.io/pypi/l/nameparser.svg
76
+ :target: https://pypi.org/project/nameparser/
77
+ .. |Downloads| image:: https://static.pepy.tech/badge/nameparser
78
+ :target: https://pepy.tech/project/nameparser
79
+ .. |Codecov| image:: https://codecov.io/gh/derek73/python-nameparser/branch/master/graph/badge.svg
80
+ :target: https://codecov.io/gh/derek73/python-nameparser
@@ -0,0 +1,40 @@
1
+ from nameparser._version import VERSION as VERSION
2
+ from nameparser._version import __version__ as __version__
3
+ from nameparser.parser import HumanName as HumanName
4
+ __author__ = "Derek Gulbranson"
5
+ __author_email__ = 'derek73@gmail.com'
6
+ __license__ = "LGPL"
7
+ __url__ = "https://github.com/derek73/python-nameparser"
8
+
9
+ from nameparser._lexicon import Lexicon
10
+ from nameparser._locale import Locale
11
+ from nameparser._parser import Parser, parse, parser_for
12
+ from nameparser._policy import (
13
+ DEFAULT_NICKNAME_DELIMITERS,
14
+ FAMILY_FIRST,
15
+ FAMILY_FIRST_GIVEN_LAST,
16
+ GIVEN_FIRST,
17
+ UNSET,
18
+ PatronymicRule,
19
+ Policy,
20
+ PolicyPatch,
21
+ )
22
+ from nameparser._types import (
23
+ Ambiguity,
24
+ AmbiguityKind,
25
+ ParsedName,
26
+ Role,
27
+ Span,
28
+ Token,
29
+ )
30
+
31
+ __all__ = [
32
+ # v1 (compatibility layer)
33
+ "HumanName",
34
+ # v2 core
35
+ "Span", "Role", "Token", "Ambiguity", "AmbiguityKind", "ParsedName",
36
+ "Lexicon", "Policy", "PolicyPatch", "PatronymicRule", "UNSET",
37
+ "GIVEN_FIRST", "FAMILY_FIRST", "FAMILY_FIRST_GIVEN_LAST",
38
+ "DEFAULT_NICKNAME_DELIMITERS", "Locale",
39
+ "Parser", "parse", "parser_for",
40
+ ]
@@ -0,0 +1,48 @@
1
+ """Command-line debug helper over the 2.0 API (migration spec §6).
2
+
3
+ python -m nameparser "Dr. Juan Q. Xavier de la Vega III"
4
+ python -m nameparser --json "Doe, John"
5
+ """
6
+ import argparse
7
+ import json
8
+
9
+ from nameparser import parse
10
+
11
+
12
+ def main(argv: list[str] | None = None) -> int:
13
+ ap = argparse.ArgumentParser(
14
+ prog="nameparser", description="Parse a personal name.")
15
+ ap.add_argument("name", help="the name string to parse")
16
+ ap.add_argument("--json", action="store_true",
17
+ help="print the component dict as JSON")
18
+ ap.add_argument("--locale", metavar="CODE",
19
+ help="parse with a locale pack (e.g. 'ru'); see "
20
+ "nameparser.locales")
21
+ args = ap.parse_args(argv)
22
+ # `is not None`, not truthiness: --locale "" must reach get() and
23
+ # exit 2 listing the codes, not silently fall back to the default
24
+ if args.locale is not None:
25
+ from nameparser import locales, parser_for
26
+ try:
27
+ parser = parser_for(locales.get(args.locale))
28
+ except KeyError as exc:
29
+ ap.error(exc.args[0]) # exits 2, message to stderr
30
+ n = parser.parse(args.name)
31
+ else:
32
+ n = parse(args.name)
33
+ if args.json:
34
+ print(json.dumps(n.as_dict(), ensure_ascii=False))
35
+ return 0
36
+ # Label each section: the two reprs are byte-identical for input
37
+ # that is already correctly cased, so without labels there is no
38
+ # telling which is the parse and which is the capitalized view.
39
+ print("Parsed:")
40
+ print(repr(n))
41
+ print("Capitalized:")
42
+ print(repr(n.capitalized()))
43
+ print("Initials:", n.initials())
44
+ return 0
45
+
46
+
47
+ if __name__ == "__main__":
48
+ raise SystemExit(main())