ycleptic 2.2.1__tar.gz → 2.2.2__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 (63) hide show
  1. {ycleptic-2.2.1 → ycleptic-2.2.2}/CHANGELOG.md +8 -0
  2. {ycleptic-2.2.1 → ycleptic-2.2.2}/PKG-INFO +8 -2
  3. {ycleptic-2.2.1 → ycleptic-2.2.2}/README.md +6 -0
  4. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/index.rst +18 -0
  5. {ycleptic-2.2.1 → ycleptic-2.2.2}/pyproject.toml +1 -1
  6. {ycleptic-2.2.1 → ycleptic-2.2.2}/tests/test_set.py +36 -0
  7. {ycleptic-2.2.1 → ycleptic-2.2.2}/ycleptic/yclept.py +15 -6
  8. {ycleptic-2.2.1 → ycleptic-2.2.2}/.envrc +0 -0
  9. {ycleptic-2.2.1 → ycleptic-2.2.2}/.github/workflows/ci.yaml +0 -0
  10. {ycleptic-2.2.1 → ycleptic-2.2.2}/.github/workflows/release.yaml +0 -0
  11. {ycleptic-2.2.1 → ycleptic-2.2.2}/.gitignore +0 -0
  12. {ycleptic-2.2.1 → ycleptic-2.2.2}/.readthedocs.yaml +0 -0
  13. {ycleptic-2.2.1 → ycleptic-2.2.2}/LICENSE +0 -0
  14. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/Makefile +0 -0
  15. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/make.bat +0 -0
  16. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/requirements.txt +0 -0
  17. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/_static/css/custom.css +0 -0
  18. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/about.rst +0 -0
  19. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/api/API.rst +0 -0
  20. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/api/ycleptic.cli.rst +0 -0
  21. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/api/ycleptic.dictthings.rst +0 -0
  22. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/api/ycleptic.errors.rst +0 -0
  23. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/api/ycleptic.makedoc.rst +0 -0
  24. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/api/ycleptic.resources.rst +0 -0
  25. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/api/ycleptic.rst +0 -0
  26. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/api/ycleptic.stringthings.rst +0 -0
  27. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/api/ycleptic.walkers.rst +0 -0
  28. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/api/ycleptic.yclept.rst +0 -0
  29. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/changelog.rst +0 -0
  30. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/conf.py +0 -0
  31. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/installation.rst +0 -0
  32. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/roadmap.md +0 -0
  33. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/usage/class.rst +0 -0
  34. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/usage/interactive_help.rst +0 -0
  35. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/usage/quickstart.rst +0 -0
  36. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/usage/resource_file.rst +0 -0
  37. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/usage/the_base_config.rst +0 -0
  38. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/usage/the_user_config.rst +0 -0
  39. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/usage/yclept_config-help.rst +0 -0
  40. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/usage/yclept_makedoc.rst +0 -0
  41. {ycleptic-2.2.1 → ycleptic-2.2.2}/docs/source/usage.rst +0 -0
  42. {ycleptic-2.2.1 → ycleptic-2.2.2}/pytest.ini +0 -0
  43. {ycleptic-2.2.1 → ycleptic-2.2.2}/scripts/release.sh +0 -0
  44. {ycleptic-2.2.1 → ycleptic-2.2.2}/tests/__init__.py +0 -0
  45. {ycleptic-2.2.1 → ycleptic-2.2.2}/tests/conftest.py +0 -0
  46. {ycleptic-2.2.1 → ycleptic-2.2.2}/tests/test_set/test_package/rootdir/README.md +0 -0
  47. {ycleptic-2.2.1 → ycleptic-2.2.2}/tests/test_set/test_package/rootdir/mypackage/__init__.py +0 -0
  48. {ycleptic-2.2.1 → ycleptic-2.2.2}/tests/test_set/test_package/rootdir/mypackage/config.py +0 -0
  49. {ycleptic-2.2.1 → ycleptic-2.2.2}/tests/test_set/test_package/rootdir/mypackage/data/__init__.py +0 -0
  50. {ycleptic-2.2.1 → ycleptic-2.2.2}/tests/test_set/test_package/rootdir/mypackage/data/base.yaml +0 -0
  51. {ycleptic-2.2.1 → ycleptic-2.2.2}/tests/test_set/test_package/rootdir/mypackage/docs/source/intro.rst +0 -0
  52. {ycleptic-2.2.1 → ycleptic-2.2.2}/tests/test_set/test_package/rootdir/mypackage/otherstuff/stuff.py +0 -0
  53. {ycleptic-2.2.1 → ycleptic-2.2.2}/tests/test_set/test_package/rootdir/setup.py +0 -0
  54. {ycleptic-2.2.1 → ycleptic-2.2.2}/ycleptic/__init__.py +0 -0
  55. {ycleptic-2.2.1 → ycleptic-2.2.2}/ycleptic/cli.py +0 -0
  56. {ycleptic-2.2.1 → ycleptic-2.2.2}/ycleptic/dictthings.py +0 -0
  57. {ycleptic-2.2.1 → ycleptic-2.2.2}/ycleptic/errors.py +0 -0
  58. {ycleptic-2.2.1 → ycleptic-2.2.2}/ycleptic/makedoc.py +0 -0
  59. {ycleptic-2.2.1 → ycleptic-2.2.2}/ycleptic/resources/__init__.py +0 -0
  60. {ycleptic-2.2.1 → ycleptic-2.2.2}/ycleptic/resources/example_base.yaml +0 -0
  61. {ycleptic-2.2.1 → ycleptic-2.2.2}/ycleptic/src/__init__.py +0 -0
  62. {ycleptic-2.2.1 → ycleptic-2.2.2}/ycleptic/stringthings.py +0 -0
  63. {ycleptic-2.2.1 → ycleptic-2.2.2}/ycleptic/walkers.py +0 -0
@@ -5,6 +5,14 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [2.2.2] - 2026-08-13
9
+
10
+ ### Added
11
+ - Status badges (PyPI version, supported Python versions, CI, documentation, license) on the README and the documentation landing page
12
+
13
+ ### Fixed
14
+ - Interactive `config-help` no longer ends with an uncaught traceback when stdin reaches EOF (piped or redirected input) or when the user presses Ctrl-C; either case now quits the session cleanly, exactly as if the user had typed `!`
15
+
8
16
  ## [2.2.1] - 2026-07-21
9
17
 
10
18
  ### Changed
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: ycleptic
3
- Version: 2.2.1
3
+ Version: 2.2.2
4
4
  Summary: A YAML-format configuration generator
5
5
  Project-URL: Source, https://github.com/cameronabrams/ycleptic
6
6
  Project-URL: Documentation, https://ycleptic.readthedocs.io/en/latest/
@@ -48,6 +48,12 @@ Description-Content-Type: text/markdown
48
48
  # Ycleptic
49
49
  > Self-documenting YAML configuration for Python applications
50
50
 
51
+ [![PyPI](https://img.shields.io/pypi/v/ycleptic)](https://pypi.org/project/ycleptic/)
52
+ [![Python versions](https://img.shields.io/pypi/pyversions/ycleptic)](https://pypi.org/project/ycleptic/)
53
+ [![CI](https://github.com/cameronabrams/ycleptic/actions/workflows/ci.yaml/badge.svg)](https://github.com/cameronabrams/ycleptic/actions/workflows/ci.yaml)
54
+ [![Documentation Status](https://readthedocs.org/projects/ycleptic/badge/?version=latest)](https://ycleptic.readthedocs.io/en/latest/?badge=latest)
55
+ [![License: MIT](https://img.shields.io/pypi/l/ycleptic)](https://github.com/cameronabrams/ycleptic/blob/main/LICENSE)
56
+
51
57
  One structured YAML file describes your application's configuration schema. From
52
58
  that single source of truth, Ycleptic gives you three things that normally live
53
59
  in three separate places:
@@ -1,6 +1,12 @@
1
1
  # Ycleptic
2
2
  > Self-documenting YAML configuration for Python applications
3
3
 
4
+ [![PyPI](https://img.shields.io/pypi/v/ycleptic)](https://pypi.org/project/ycleptic/)
5
+ [![Python versions](https://img.shields.io/pypi/pyversions/ycleptic)](https://pypi.org/project/ycleptic/)
6
+ [![CI](https://github.com/cameronabrams/ycleptic/actions/workflows/ci.yaml/badge.svg)](https://github.com/cameronabrams/ycleptic/actions/workflows/ci.yaml)
7
+ [![Documentation Status](https://readthedocs.org/projects/ycleptic/badge/?version=latest)](https://ycleptic.readthedocs.io/en/latest/?badge=latest)
8
+ [![License: MIT](https://img.shields.io/pypi/l/ycleptic)](https://github.com/cameronabrams/ycleptic/blob/main/LICENSE)
9
+
4
10
  One structured YAML file describes your application's configuration schema. From
5
11
  that single source of truth, Ycleptic gives you three things that normally live
6
12
  in three separate places:
@@ -1,6 +1,24 @@
1
1
  ycleptic
2
2
  ========
3
3
 
4
+ |pypi| |pyversions| |ci| |docs| |license|
5
+
6
+ .. |pypi| image:: https://img.shields.io/pypi/v/ycleptic
7
+ :target: https://pypi.org/project/ycleptic/
8
+ :alt: PyPI version
9
+ .. |pyversions| image:: https://img.shields.io/pypi/pyversions/ycleptic
10
+ :target: https://pypi.org/project/ycleptic/
11
+ :alt: Supported Python versions
12
+ .. |ci| image:: https://github.com/cameronabrams/ycleptic/actions/workflows/ci.yaml/badge.svg
13
+ :target: https://github.com/cameronabrams/ycleptic/actions/workflows/ci.yaml
14
+ :alt: CI status
15
+ .. |docs| image:: https://readthedocs.org/projects/ycleptic/badge/?version=latest
16
+ :target: https://ycleptic.readthedocs.io/en/latest/?badge=latest
17
+ :alt: Documentation status
18
+ .. |license| image:: https://img.shields.io/pypi/l/ycleptic
19
+ :target: https://github.com/cameronabrams/ycleptic/blob/main/LICENSE
20
+ :alt: License: MIT
21
+
4
22
  **Ycleptic** (ee-KLEP-tic) turns a single, structured YAML file into a self-documenting configuration system for your Python application. From one schema file you get three things at once: validation of a user's config (with defaults, allowed value ``choices``, and required parameters), an automatically generated RST/Sphinx documentation tree, and an interactive help explorer for your users. Because the schema is data rather than Python code, editing that one file keeps validation, documentation, and help in lockstep.
5
23
 
6
24
  Ycleptic is intentionally lightweight on validation and focuses on the *spec-as-documentation* workflow — a good fit for scientific and command-line applications whose users are domain experts. If you need heavyweight validation and coercion, `pydantic <https://docs.pydantic.dev/>`_ or `jsonschema <https://python-jsonschema.readthedocs.io/>`_ are better suited; for composable, hierarchical config, see `Hydra <https://hydra.cc/>`_.
@@ -3,7 +3,7 @@ requires = ["hatchling"]
3
3
  build-backend = "hatchling.build"
4
4
  [project]
5
5
  name = "ycleptic"
6
- version = "2.2.1"
6
+ version = "2.2.2"
7
7
  authors = [
8
8
  { name="Cameron F Abrams", email="cfa22@drexel.edu" },
9
9
  ]
@@ -270,6 +270,42 @@ base|attribute_2->attribute_2a
270
270
  with open('console-out.txt', 'r') as f:
271
271
  self.assertEqual(f.read(), ref_str)
272
272
 
273
+ def test_console_help_eof_quits_cleanly(self):
274
+ """EOF on stdin ends an interactive help session as if the user typed '!'."""
275
+ from unittest.mock import patch
276
+
277
+ Y = Yclept(BFILE)
278
+ with open('console-out.txt', 'w') as f:
279
+ with redirect_stdout(f):
280
+ with patch('builtins.input', side_effect=EOFError):
281
+ Y.console_help([], interactive_prompt='help: ')
282
+ with open('console-out.txt', 'r') as f:
283
+ self.assertIn('! quit', f.read())
284
+
285
+ def test_console_help_eof_after_bad_choice_quits_cleanly(self):
286
+ """EOF also ends the session from the re-prompt after an unrecognized choice."""
287
+ from unittest.mock import patch
288
+
289
+ Y = Yclept(BFILE)
290
+ with open('console-out.txt', 'w') as f:
291
+ with redirect_stdout(f):
292
+ with patch('builtins.input', side_effect=['bogus', EOFError]):
293
+ Y.console_help([], interactive_prompt='help: ')
294
+ with open('console-out.txt', 'r') as f:
295
+ self.assertIn('bogus not recognized.', f.read())
296
+
297
+ def test_console_help_keyboard_interrupt_quits_cleanly(self):
298
+ """Ctrl-C at the prompt ends the session cleanly rather than raising."""
299
+ from unittest.mock import patch
300
+
301
+ Y = Yclept(BFILE)
302
+ with open('console-out.txt', 'w') as f:
303
+ with redirect_stdout(f):
304
+ with patch('builtins.input', side_effect=KeyboardInterrupt):
305
+ Y.console_help([], interactive_prompt='help: ')
306
+ with open('console-out.txt', 'r') as f:
307
+ self.assertIn('! quit', f.read())
308
+
273
309
  def test_makedoc(self):
274
310
  Y = Yclept(BFILE)
275
311
  Y.make_doctree('ydoc')
@@ -201,23 +201,32 @@ class Yclept(UserDict):
201
201
  c = ''
202
202
  H.write_func(f' {m}{c}{a}')
203
203
 
204
+ def _prompt_choice(self):
205
+ H: Namespace = self.H
206
+ choice = '!'
207
+ if H.interactive_prompt != '':
208
+ try:
209
+ choice = input(H.interactive_prompt)
210
+ except (EOFError, KeyboardInterrupt):
211
+ # no more input, or the user interrupted; quit as though
212
+ # the user had typed '!'
213
+ H.write_func('')
214
+ choice = '!'
215
+ return choice
216
+
204
217
  def _get_help_choice(self, init_list: list[str]):
205
218
  H: Namespace = self.H
206
219
  if len(init_list) > 0:
207
220
  choice = init_list.pop()
208
221
  else:
209
- choice = '!'
210
- if H.interactive_prompt != '':
211
- choice = input(H.interactive_prompt)
222
+ choice = self._prompt_choice()
212
223
  while choice == '' or choice not in [x['name'] for x in H.base] + ['..', '!']:
213
224
  if choice != '':
214
225
  H.write_func(f'{choice} not recognized.')
215
226
  if len(init_list) > 0:
216
227
  choice = init_list.pop()
217
228
  else:
218
- choice = '!'
219
- if H.interactive_prompt != '':
220
- choice = input(H.interactive_prompt)
229
+ choice = self._prompt_choice()
221
230
  return choice
222
231
 
223
232
  def _help(self):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes