version-query 1.7.0rc1__tar.gz → 1.7.1__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 (35) hide show
  1. {version_query-1.7.0rc1 → version_query-1.7.1}/NOTICE +1 -1
  2. {version_query-1.7.0rc1/version_query.egg-info → version_query-1.7.1}/PKG-INFO +7 -7
  3. {version_query-1.7.0rc1 → version_query-1.7.1}/README.rst +1 -1
  4. {version_query-1.7.0rc1 → version_query-1.7.1}/setup.py +1 -1
  5. {version_query-1.7.0rc1 → version_query-1.7.1}/test/__init__.py +3 -0
  6. {version_query-1.7.0rc1 → version_query-1.7.1}/test/examples.py +1 -1
  7. {version_query-1.7.0rc1 → version_query-1.7.1}/test/test_cli.py +16 -0
  8. {version_query-1.7.0rc1 → version_query-1.7.1}/test/test_git.py +14 -0
  9. {version_query-1.7.0rc1 → version_query-1.7.1}/test/test_query.py +17 -0
  10. {version_query-1.7.0rc1 → version_query-1.7.1}/test/test_version.py +6 -9
  11. {version_query-1.7.0rc1 → version_query-1.7.1}/version_query/git_query.py +7 -3
  12. {version_query-1.7.0rc1 → version_query-1.7.1}/version_query/main.py +1 -1
  13. {version_query-1.7.0rc1 → version_query-1.7.1/version_query.egg-info}/PKG-INFO +7 -7
  14. {version_query-1.7.0rc1 → version_query-1.7.1}/LICENSE +0 -0
  15. {version_query-1.7.0rc1 → version_query-1.7.1}/MANIFEST.in +0 -0
  16. {version_query-1.7.0rc1 → version_query-1.7.1}/pyproject.toml +0 -0
  17. {version_query-1.7.0rc1 → version_query-1.7.1}/requirements.txt +0 -0
  18. {version_query-1.7.0rc1 → version_query-1.7.1}/requirements_test.txt +0 -0
  19. {version_query-1.7.0rc1 → version_query-1.7.1}/setup.cfg +0 -0
  20. {version_query-1.7.0rc1 → version_query-1.7.1}/test/test_local_git_version.py +0 -0
  21. {version_query-1.7.0rc1 → version_query-1.7.1}/test/test_packaging.py +0 -0
  22. {version_query-1.7.0rc1 → version_query-1.7.1}/version_query/__init__.py +0 -0
  23. {version_query-1.7.0rc1 → version_query-1.7.1}/version_query/__main__.py +0 -0
  24. {version_query-1.7.0rc1 → version_query-1.7.1}/version_query/_version.py +0 -0
  25. {version_query-1.7.0rc1 → version_query-1.7.1}/version_query/local_git_version.py +0 -0
  26. {version_query-1.7.0rc1 → version_query-1.7.1}/version_query/parser.py +0 -0
  27. {version_query-1.7.0rc1 → version_query-1.7.1}/version_query/patterns.py +0 -0
  28. {version_query-1.7.0rc1 → version_query-1.7.1}/version_query/py.typed +0 -0
  29. {version_query-1.7.0rc1 → version_query-1.7.1}/version_query/py_query.py +0 -0
  30. {version_query-1.7.0rc1 → version_query-1.7.1}/version_query/query.py +0 -0
  31. {version_query-1.7.0rc1 → version_query-1.7.1}/version_query/version.py +0 -0
  32. {version_query-1.7.0rc1 → version_query-1.7.1}/version_query.egg-info/SOURCES.txt +0 -0
  33. {version_query-1.7.0rc1 → version_query-1.7.1}/version_query.egg-info/dependency_links.txt +0 -0
  34. {version_query-1.7.0rc1 → version_query-1.7.1}/version_query.egg-info/requires.txt +0 -0
  35. {version_query-1.7.0rc1 → version_query-1.7.1}/version_query.egg-info/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  version-query
2
- Copyright (c) 2017-2025 Mateusz Bysiek https://mbdevpl.github.io/
2
+ Copyright (c) 2017-2026 Mateusz Bysiek https://mbdevpl.github.io/
3
3
  Copyright (c) 2020 John Vandenberg https://github.com/jayvdb
4
4
 
5
5
  Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: version-query
3
- Version: 1.7.0rc1
3
+ Version: 1.7.1
4
4
  Summary: Zero-overhead package versioning for Python.
5
5
  Home-page: https://github.com/mbdevpl/version-query
6
6
  Download-URL:
@@ -17,18 +17,18 @@ Classifier: Natural Language :: English
17
17
  Classifier: Operating System :: MacOS
18
18
  Classifier: Operating System :: Microsoft :: Windows
19
19
  Classifier: Operating System :: POSIX :: Linux
20
- Classifier: Programming Language :: Python :: 3.9
21
20
  Classifier: Programming Language :: Python :: 3.10
22
21
  Classifier: Programming Language :: Python :: 3.11
23
22
  Classifier: Programming Language :: Python :: 3.12
24
23
  Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: 3.14
25
25
  Classifier: Programming Language :: Python :: 3 :: Only
26
26
  Classifier: Topic :: Software Development :: Version Control
27
27
  Classifier: Topic :: Software Development :: Version Control :: Git
28
28
  Classifier: Topic :: System :: Software Distribution
29
29
  Classifier: Topic :: Utilities
30
30
  Classifier: Typing :: Typed
31
- Requires-Python: >=3.9
31
+ Requires-Python: >=3.10
32
32
  Description-Content-Type: text/x-rst; charset=UTF-8
33
33
  License-File: LICENSE
34
34
  License-File: NOTICE
@@ -81,7 +81,7 @@ Zero-overhead package versioning for Python.
81
81
  :alt: grade from Codacy
82
82
 
83
83
  .. image:: https://img.shields.io/github/license/mbdevpl/version-query.svg
84
- :target: https://github.com/mbdevpl/version-query/blob/v1.7.0rc1/NOTICE
84
+ :target: https://github.com/mbdevpl/version-query/blob/v1.7.1/NOTICE
85
85
  :alt: license
86
86
 
87
87
  Package versioning toolkit for Python which relies on git tags, git history traversal
@@ -570,10 +570,10 @@ using version-query without any issues.
570
570
  Requirements
571
571
  ============
572
572
 
573
- Python version 3.9 or later.
573
+ Python version 3.10 or later.
574
574
 
575
- Python libraries as specified in `requirements.txt <https://github.com/mbdevpl/version-query/blob/v1.7.0rc1/requirements.txt>`_.
575
+ Python libraries as specified in `requirements.txt <https://github.com/mbdevpl/version-query/blob/v1.7.1/requirements.txt>`_.
576
576
 
577
- Building and running tests additionally requires packages listed in `requirements_test.txt <https://github.com/mbdevpl/version-query/blob/v1.7.0rc1/requirements_test.txt>`_.
577
+ Building and running tests additionally requires packages listed in `requirements_test.txt <https://github.com/mbdevpl/version-query/blob/v1.7.1/requirements_test.txt>`_.
578
578
 
579
579
  Tested on Linux, macOS and Windows.
@@ -517,7 +517,7 @@ using version-query without any issues.
517
517
  Requirements
518
518
  ============
519
519
 
520
- Python version 3.9 or later.
520
+ Python version 3.10 or later.
521
521
 
522
522
  Python libraries as specified in `<requirements.txt>`_.
523
523
 
@@ -19,11 +19,11 @@ class Package(boilerplates.setup.Package):
19
19
  'Operating System :: MacOS',
20
20
  'Operating System :: Microsoft :: Windows',
21
21
  'Operating System :: POSIX :: Linux',
22
- 'Programming Language :: Python :: 3.9',
23
22
  'Programming Language :: Python :: 3.10',
24
23
  'Programming Language :: Python :: 3.11',
25
24
  'Programming Language :: Python :: 3.12',
26
25
  'Programming Language :: Python :: 3.13',
26
+ 'Programming Language :: Python :: 3.14',
27
27
  'Programming Language :: Python :: 3 :: Only',
28
28
  'Topic :: Software Development :: Version Control',
29
29
  'Topic :: Software Development :: Version Control :: Git',
@@ -12,3 +12,6 @@ class TestsLogging(Logging):
12
12
 
13
13
 
14
14
  TestsLogging.configure()
15
+ logging.getLogger('version_query.git_query').setLevel(logging.INFO)
16
+ logging.getLogger('version_query.parser').setLevel(logging.INFO)
17
+ logging.getLogger('version_query.version').setLevel(logging.INFO)
@@ -11,7 +11,7 @@ from version_query.version import VersionComponent
11
11
 
12
12
  _GIT_REPOS_ROOT = pathlib.Path(os.environ.get('EXAMPLE_PROJECTS_PATH', '..')).resolve()
13
13
 
14
- GIT_REPO_EXAMPLES = list(_ for _ in _GIT_REPOS_ROOT.glob('**/.git') if _.is_dir())
14
+ GIT_REPO_EXAMPLES = list(_.parent for _ in _GIT_REPOS_ROOT.glob('**/.git') if _.is_dir())
15
15
 
16
16
 
17
17
  def python_lib_dir() -> pathlib.Path:
@@ -3,6 +3,7 @@
3
3
  import contextlib
4
4
  import io
5
5
  import logging
6
+ import os
6
7
  import runpy
7
8
  import sys
8
9
  import unittest
@@ -42,6 +43,9 @@ class Tests(unittest.TestCase):
42
43
  def test_not_as_main(self): # pylint: disable = no-self-use
43
44
  run_module('version_query', run_name='__not_main__')
44
45
 
46
+ @unittest.skipUnless(
47
+ os.environ.get('TEST_CLI') or os.environ.get('CI'),
48
+ 'skipping CLI test which breaks test logging')
45
49
  def test_help(self):
46
50
  sio = io.StringIO()
47
51
  with contextlib.redirect_stderr(sio), preserve_logger_level('version_query'), \
@@ -49,6 +53,9 @@ class Tests(unittest.TestCase):
49
53
  run_module('version_query')
50
54
  _LOG.info('%s', sio.getvalue())
51
55
 
56
+ @unittest.skipUnless(
57
+ os.environ.get('TEST_CLI') or os.environ.get('CI'),
58
+ 'skipping CLI test which breaks test logging')
52
59
  def test_bad_usage(self):
53
60
  sio = io.StringIO()
54
61
  with contextlib.redirect_stderr(sio), preserve_logger_level('version_query'), \
@@ -56,6 +63,9 @@ class Tests(unittest.TestCase):
56
63
  run_module('version_query', '-p', '-i', '.')
57
64
  _LOG.info('%s', sio.getvalue())
58
65
 
66
+ @unittest.skipUnless(
67
+ os.environ.get('TEST_CLI') or os.environ.get('CI'),
68
+ 'skipping CLI test which breaks test logging')
59
69
  def test_here(self):
60
70
  sio = io.StringIO()
61
71
  with temporarily_set_logger_level('version_query', logging.ERROR), \
@@ -64,6 +74,9 @@ class Tests(unittest.TestCase):
64
74
  self.assertEqual(sio.getvalue().rstrip(), query_caller().to_str())
65
75
  self.assertEqual(sio.getvalue().rstrip(), query_version_str())
66
76
 
77
+ @unittest.skipUnless(
78
+ os.environ.get('TEST_CLI') or os.environ.get('CI'),
79
+ 'skipping CLI test which breaks test logging')
67
80
  def test_increment_here(self):
68
81
  sio = io.StringIO()
69
82
  with temporarily_set_logger_level('version_query', logging.ERROR), \
@@ -72,6 +85,9 @@ class Tests(unittest.TestCase):
72
85
  self.assertEqual(sio.getvalue().rstrip(),
73
86
  query_caller().increment(VersionComponent.Patch).to_str())
74
87
 
88
+ @unittest.skipUnless(
89
+ os.environ.get('TEST_CLI') or os.environ.get('CI'),
90
+ 'skipping CLI test which breaks test logging')
75
91
  def test_predict_here(self):
76
92
  sio = io.StringIO()
77
93
  with temporarily_set_logger_level('version_query', logging.ERROR), \
@@ -2,6 +2,7 @@
2
2
 
3
3
  import itertools
4
4
  import logging
5
+ import os
5
6
  import platform
6
7
  import unittest
7
8
 
@@ -79,6 +80,19 @@ class Tests(boilerplates.git_repo_tests.GitRepoTests):
79
80
  version.local = (f'git{self.repo_head_hexsha}',)
80
81
  self.assertEqual(version, upcoming_version)
81
82
 
83
+ def test_too_short_version_tag(self):
84
+ self.git_commit_new_file()
85
+ self.repo.create_tag('v1.0')
86
+ self.git_commit_new_file()
87
+ self.repo.create_tag('v')
88
+ self.git_commit_new_file()
89
+ self.repo.create_tag('ver')
90
+ current_version = query_git_repo(self.repo_path)
91
+ _LOG.debug('current version is %s', current_version)
92
+ self.assertEqual(current_version.to_str(), '1.0')
93
+
94
+ @unittest.skipUnless(
95
+ os.environ.get('TEST_LONG') or os.environ.get('CI'), 'skipping long test')
82
96
  def test_too_long_no_tag(self):
83
97
  self.git_commit_new_file()
84
98
  self.repo.create_tag('v4.0.0')
@@ -23,6 +23,15 @@ _LOG = logging.getLogger(__name__)
23
23
 
24
24
 
25
25
  class Tests(unittest.TestCase):
26
+ """Test querying tools from several modules.
27
+
28
+ Note:
29
+
30
+ Tests that run setup script directly fail due to conflict between distutils used internally
31
+ by setuptools and distutils used internally by pip. For more information, see:
32
+ - https://github.com/pypa/setuptools/issues/3297
33
+ - https://github.com/pypa/pip/issues/8761
34
+ """
26
35
 
27
36
  def _check_examples_count(self, description, examples):
28
37
  lvl = logging.WARNING if len(examples) < 10 else logging.INFO
@@ -51,8 +60,11 @@ class Tests(unittest.TestCase):
51
60
  else:
52
61
  _LOG.debug('%s: %s', path, version)
53
62
 
63
+ @unittest.skipUnless(
64
+ os.environ.get('TEST_LONG') or os.environ.get('CI'), 'skipping long test')
54
65
  def test_query_git_repo(self):
55
66
  self._check_examples_count('git repo', GIT_REPO_EXAMPLES)
67
+ _LOG.debug('testing query_git_repo() on %i repositories', len(GIT_REPO_EXAMPLES))
56
68
  self._query_test_case(GIT_REPO_EXAMPLES, query_git_repo)
57
69
 
58
70
  def test_predict_caller_bad(self):
@@ -72,7 +84,10 @@ class Tests(unittest.TestCase):
72
84
  _LOG.warning('removed %s from sys.path', project_path_str)
73
85
  project_file_path.unlink()
74
86
 
87
+ @unittest.skipUnless(
88
+ os.environ.get('TEST_LONG') or os.environ.get('CI'), 'skipping long test')
75
89
  def test_predict_git_repo(self):
90
+ _LOG.debug('testing predict_git_repo() on %i repositories', len(GIT_REPO_EXAMPLES))
76
91
  self._query_test_case(GIT_REPO_EXAMPLES, predict_git_repo)
77
92
 
78
93
  @unittest.skipIf(not METADATA_JSON_EXAMPLE_PATHS, 'no "metadata.json" files found')
@@ -87,6 +102,7 @@ class Tests(unittest.TestCase):
87
102
 
88
103
  @unittest.skipUnless(
89
104
  os.environ.get('TEST_PACKAGING') or os.environ.get('CI'), 'skipping packaging test')
105
+ @unittest.skipIf(sys.version_info < (3, 12), 'requires Python 3.12+')
90
106
  def test_query_pkg_info_current(self):
91
107
  with preserve_logger_level('version_query'):
92
108
  run_module('setup', 'build')
@@ -120,6 +136,7 @@ class Tests(unittest.TestCase):
120
136
 
121
137
  @unittest.skipUnless(
122
138
  os.environ.get('TEST_PACKAGING') or os.environ.get('CI'), 'skipping packaging test')
139
+ @unittest.skipIf(sys.version_info < (3, 12), 'requires Python 3.12+')
123
140
  def test_query_package_folder_current(self):
124
141
  with preserve_logger_level('version_query'):
125
142
  run_module('setup', 'build')
@@ -4,7 +4,6 @@ import logging
4
4
  import unittest
5
5
 
6
6
  import packaging.version
7
- import pkg_resources
8
7
  import semver
9
8
 
10
9
  from version_query.version import VersionComponent, Version
@@ -23,13 +22,12 @@ class Tests(unittest.TestCase):
23
22
  for version_str, (args, kwargs) in STR_CASES.items():
24
23
  version_tuple = case_to_version_tuple(args, kwargs)
25
24
  with self.subTest(version_str=version_str, version_tuple=version_tuple):
26
-
27
25
  py_version = \
28
- pkg_resources.parse_version(version_str)
26
+ packaging.version.parse(version_str)
29
27
  _LOG.debug('packaging parsed version string %s into %s: %s',
30
28
  repr(version_str), type(py_version), py_version)
31
- # self.assertIsInstance(
32
- # py_version, packaging.version.Version, msg=(type(py_version), py_version))
29
+ self.assertIsInstance(
30
+ py_version, packaging.version.Version, msg=(type(py_version), py_version))
33
31
 
34
32
  try:
35
33
  sem_version = semver.VersionInfo.parse(version_str)
@@ -38,8 +36,8 @@ class Tests(unittest.TestCase):
38
36
  else:
39
37
  _LOG.debug('semver parsed version string %s into %s: %s',
40
38
  repr(version_str), type(sem_version), sem_version)
41
- # self.assertIsInstance(
42
- # sem_version, semver.VersionInfo, msg=(type(sem_version), sem_version))
39
+ self.assertIsInstance(
40
+ sem_version, semver.VersionInfo, msg=(type(sem_version), sem_version))
43
41
 
44
42
  self.assertEqual(Version.from_str(version_str).to_tuple(), version_tuple)
45
43
 
@@ -59,7 +57,7 @@ class Tests(unittest.TestCase):
59
57
  py_version = packaging.version.Version(version_str)
60
58
  self.assertEqual(Version.from_py_version(py_version).to_tuple(),
61
59
  version_tuple, py_version)
62
- py_version_setuptools = pkg_resources.parse_version(version_str)
60
+ py_version_setuptools = packaging.version.parse(version_str)
63
61
  self.assertEqual(Version.from_py_version(py_version_setuptools).to_tuple(),
64
62
  version_tuple, py_version_setuptools)
65
63
 
@@ -71,7 +69,6 @@ class Tests(unittest.TestCase):
71
69
  py_version = packaging.version.Version(version_str)
72
70
  self.assertEqual(version.to_py_version(), py_version,
73
71
  msg=(version.to_py_version(), py_version))
74
- # py_version_setuptools = pkg_resources.parse_version(version_str)
75
72
 
76
73
  def test_from_sem_version(self):
77
74
  for version_str, (args, kwargs) in COMPATIBLE_STR_CASES.items():
@@ -15,19 +15,23 @@ _LOG = logging.getLogger(__name__)
15
15
  def preprocess_git_version_tag(tag: str):
16
16
  """Remove a prefix from a version tag."""
17
17
  if tag.startswith('ver'):
18
+ if len(tag) == 3:
19
+ raise ValueError(f'the tag "{tag}" does not contain any version information')
18
20
  return tag[3:]
19
21
  if tag.startswith('v'):
22
+ if len(tag) == 1:
23
+ raise ValueError(f'the tag "{tag}" does not contain any version information')
20
24
  return tag[1:]
21
25
  if tag and tag[0] in ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'):
22
26
  return tag
23
- raise ValueError(f'given tag "{tag}" does not appear to be a version tag')
27
+ raise ValueError(f'the tag "{tag}" does not appear to be a version tag')
24
28
 
25
29
 
26
30
  def _git_version_tags(repo: git.Repo) -> t.Mapping[git.Tag, Version]:
27
31
  versions = {}
28
32
  for tag in repo.tags:
29
33
  try:
30
- tag_str = preprocess_git_version_tag(str(tag))
34
+ tag_str = preprocess_git_version_tag(tag.name)
31
35
  except ValueError:
32
36
  _LOG.debug('%s: ignoring non-version tag %s', repo, tag)
33
37
  continue
@@ -35,7 +39,7 @@ def _git_version_tags(repo: git.Repo) -> t.Mapping[git.Tag, Version]:
35
39
  versions[tag] = Version.from_str(tag_str)
36
40
  except ValueError:
37
41
  # except packaging.version.InvalidVersion:
38
- _LOG.warning('%s: failed to convert %s to version', repo, tag_str)
42
+ _LOG.warning('%s: failed to convert %s (%r) to version', repo, tag.name, tag_str)
39
43
  continue
40
44
  return versions
41
45
 
@@ -20,7 +20,7 @@ def main(args=None, namespace=None) -> None:
20
20
  description='''Tool for querying current versions of Python packages. Use LOGGING_LEVEL
21
21
  environment variable to adjust logging level.''',
22
22
  epilog=make_copyright_notice(
23
- 2017, 2025, author='the contributors', url='https://github.com/mbdevpl/version-query'),
23
+ 2017, 2026, author='the contributors', url='https://github.com/mbdevpl/version-query'),
24
24
  formatter_class=argparse.ArgumentDefaultsHelpFormatter)
25
25
  add_version_option(parser, VERSION)
26
26
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: version-query
3
- Version: 1.7.0rc1
3
+ Version: 1.7.1
4
4
  Summary: Zero-overhead package versioning for Python.
5
5
  Home-page: https://github.com/mbdevpl/version-query
6
6
  Download-URL:
@@ -17,18 +17,18 @@ Classifier: Natural Language :: English
17
17
  Classifier: Operating System :: MacOS
18
18
  Classifier: Operating System :: Microsoft :: Windows
19
19
  Classifier: Operating System :: POSIX :: Linux
20
- Classifier: Programming Language :: Python :: 3.9
21
20
  Classifier: Programming Language :: Python :: 3.10
22
21
  Classifier: Programming Language :: Python :: 3.11
23
22
  Classifier: Programming Language :: Python :: 3.12
24
23
  Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: 3.14
25
25
  Classifier: Programming Language :: Python :: 3 :: Only
26
26
  Classifier: Topic :: Software Development :: Version Control
27
27
  Classifier: Topic :: Software Development :: Version Control :: Git
28
28
  Classifier: Topic :: System :: Software Distribution
29
29
  Classifier: Topic :: Utilities
30
30
  Classifier: Typing :: Typed
31
- Requires-Python: >=3.9
31
+ Requires-Python: >=3.10
32
32
  Description-Content-Type: text/x-rst; charset=UTF-8
33
33
  License-File: LICENSE
34
34
  License-File: NOTICE
@@ -81,7 +81,7 @@ Zero-overhead package versioning for Python.
81
81
  :alt: grade from Codacy
82
82
 
83
83
  .. image:: https://img.shields.io/github/license/mbdevpl/version-query.svg
84
- :target: https://github.com/mbdevpl/version-query/blob/v1.7.0rc1/NOTICE
84
+ :target: https://github.com/mbdevpl/version-query/blob/v1.7.1/NOTICE
85
85
  :alt: license
86
86
 
87
87
  Package versioning toolkit for Python which relies on git tags, git history traversal
@@ -570,10 +570,10 @@ using version-query without any issues.
570
570
  Requirements
571
571
  ============
572
572
 
573
- Python version 3.9 or later.
573
+ Python version 3.10 or later.
574
574
 
575
- Python libraries as specified in `requirements.txt <https://github.com/mbdevpl/version-query/blob/v1.7.0rc1/requirements.txt>`_.
575
+ Python libraries as specified in `requirements.txt <https://github.com/mbdevpl/version-query/blob/v1.7.1/requirements.txt>`_.
576
576
 
577
- Building and running tests additionally requires packages listed in `requirements_test.txt <https://github.com/mbdevpl/version-query/blob/v1.7.0rc1/requirements_test.txt>`_.
577
+ Building and running tests additionally requires packages listed in `requirements_test.txt <https://github.com/mbdevpl/version-query/blob/v1.7.1/requirements_test.txt>`_.
578
578
 
579
579
  Tested on Linux, macOS and Windows.
File without changes