backlogops 0.9__tar.gz → 1.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 (69) hide show
  1. {backlogops-0.9 → backlogops-1.1}/PKG-INFO +8 -11
  2. {backlogops-0.9 → backlogops-1.1}/README_pypi.md +4 -7
  3. {backlogops-0.9 → backlogops-1.1}/backlogops.egg-info/PKG-INFO +8 -11
  4. {backlogops-0.9 → backlogops-1.1}/backlogops.egg-info/requires.txt +3 -3
  5. {backlogops-0.9 → backlogops-1.1}/setup.py +5 -5
  6. {backlogops-0.9 → backlogops-1.1}/src/backlogops/__init__.py +3 -4
  7. {backlogops-0.9 → backlogops-1.1}/src/backlogops/backlog_ops_config.py +21 -2
  8. {backlogops-0.9 → backlogops-1.1}/src/backlogops/config_descriptions.py +23 -13
  9. backlogops-1.1/src/backlogops/config_editing.py +110 -0
  10. backlogops-0.9/src/backlogops/config_editing.py +0 -149
  11. {backlogops-0.9 → backlogops-1.1}/LICENSE.txt +0 -0
  12. {backlogops-0.9 → backlogops-1.1}/backlogops.egg-info/SOURCES.txt +0 -0
  13. {backlogops-0.9 → backlogops-1.1}/backlogops.egg-info/dependency_links.txt +0 -0
  14. {backlogops-0.9 → backlogops-1.1}/backlogops.egg-info/top_level.txt +0 -0
  15. {backlogops-0.9 → backlogops-1.1}/pyproject.toml +0 -0
  16. {backlogops-0.9 → backlogops-1.1}/setup.cfg +0 -0
  17. {backlogops-0.9 → backlogops-1.1}/src/backlogops/apply_format_rules.py +0 -0
  18. {backlogops-0.9 → backlogops-1.1}/src/backlogops/available_teams.py +0 -0
  19. {backlogops-0.9 → backlogops-1.1}/src/backlogops/available_teams_config.py +0 -0
  20. {backlogops-0.9 → backlogops-1.1}/src/backlogops/backlog.py +0 -0
  21. {backlogops-0.9 → backlogops-1.1}/src/backlogops/backlog_helpers.py +0 -0
  22. {backlogops-0.9 → backlogops-1.1}/src/backlogops/backlog_in_release_order.py +0 -0
  23. {backlogops-0.9 → backlogops-1.1}/src/backlogops/backlog_ops_wizard.py +0 -0
  24. {backlogops-0.9 → backlogops-1.1}/src/backlogops/backlog_releases.py +0 -0
  25. {backlogops-0.9 → backlogops-1.1}/src/backlogops/backlog_releases_io.py +0 -0
  26. {backlogops-0.9 → backlogops-1.1}/src/backlogops/blo_version_reporter.py +0 -0
  27. {backlogops-0.9 → backlogops-1.1}/src/backlogops/config_file_io.py +0 -0
  28. {backlogops-0.9 → backlogops-1.1}/src/backlogops/date_ranges.py +0 -0
  29. {backlogops-0.9 → backlogops-1.1}/src/backlogops/demo_backlog.py +0 -0
  30. {backlogops-0.9 → backlogops-1.1}/src/backlogops/estimate_ready_date.py +0 -0
  31. {backlogops-0.9 → backlogops-1.1}/src/backlogops/format_rules.py +0 -0
  32. {backlogops-0.9 → backlogops-1.1}/src/backlogops/io_config.py +0 -0
  33. {backlogops-0.9 → backlogops-1.1}/src/backlogops/io_preset_wizard.py +0 -0
  34. {backlogops-0.9 → backlogops-1.1}/src/backlogops/jira_connect.py +0 -0
  35. {backlogops-0.9 → backlogops-1.1}/src/backlogops/jira_io_config.py +0 -0
  36. {backlogops-0.9 → backlogops-1.1}/src/backlogops/jira_order_releases.py +0 -0
  37. {backlogops-0.9 → backlogops-1.1}/src/backlogops/jira_rank_backlog.py +0 -0
  38. {backlogops-0.9 → backlogops-1.1}/src/backlogops/jira_rank_by_keys.py +0 -0
  39. {backlogops-0.9 → backlogops-1.1}/src/backlogops/jira_rank_move_keys.py +0 -0
  40. {backlogops-0.9 → backlogops-1.1}/src/backlogops/jira_read.py +0 -0
  41. {backlogops-0.9 → backlogops-1.1}/src/backlogops/jira_rename_releases.py +0 -0
  42. {backlogops-0.9 → backlogops-1.1}/src/backlogops/jira_search.py +0 -0
  43. {backlogops-0.9 → backlogops-1.1}/src/backlogops/jira_token.py +0 -0
  44. {backlogops-0.9 → backlogops-1.1}/src/backlogops/jira_update_backlog.py +0 -0
  45. {backlogops-0.9 → backlogops-1.1}/src/backlogops/jira_update_releases.py +0 -0
  46. {backlogops-0.9 → backlogops-1.1}/src/backlogops/jira_wizard.py +0 -0
  47. {backlogops-0.9 → backlogops-1.1}/src/backlogops/jira_write.py +0 -0
  48. {backlogops-0.9 → backlogops-1.1}/src/backlogops/jira_write_fields.py +0 -0
  49. {backlogops-0.9 → backlogops-1.1}/src/backlogops/jira_write_format.py +0 -0
  50. {backlogops-0.9 → backlogops-1.1}/src/backlogops/jira_write_releases.py +0 -0
  51. {backlogops-0.9 → backlogops-1.1}/src/backlogops/key_list_io.py +0 -0
  52. {backlogops-0.9 → backlogops-1.1}/src/backlogops/levels.py +0 -0
  53. {backlogops-0.9 → backlogops-1.1}/src/backlogops/move_keys_first.py +0 -0
  54. {backlogops-0.9 → backlogops-1.1}/src/backlogops/name_list_io.py +0 -0
  55. {backlogops-0.9 → backlogops-1.1}/src/backlogops/no_text_io.py +0 -0
  56. {backlogops-0.9 → backlogops-1.1}/src/backlogops/order_by_dependencies.py +0 -0
  57. {backlogops-0.9 → backlogops-1.1}/src/backlogops/person.py +0 -0
  58. {backlogops-0.9 → backlogops-1.1}/src/backlogops/py.typed +0 -0
  59. {backlogops-0.9 → backlogops-1.1}/src/backlogops/release_backlog_updates.py +0 -0
  60. {backlogops-0.9 → backlogops-1.1}/src/backlogops/release_change_io.py +0 -0
  61. {backlogops-0.9 → backlogops-1.1}/src/backlogops/releases.py +0 -0
  62. {backlogops-0.9 → backlogops-1.1}/src/backlogops/rename_list_io.py +0 -0
  63. {backlogops-0.9 → backlogops-1.1}/src/backlogops/table_create.py +0 -0
  64. {backlogops-0.9 → backlogops-1.1}/src/backlogops/table_rows.py +0 -0
  65. {backlogops-0.9 → backlogops-1.1}/src/backlogops/team.py +0 -0
  66. {backlogops-0.9 → backlogops-1.1}/src/backlogops/wizard_forms.py +0 -0
  67. {backlogops-0.9 → backlogops-1.1}/src/backlogops/wizard_helpers.py +0 -0
  68. {backlogops-0.9 → backlogops-1.1}/src/backlogops/wizard_navigator.py +0 -0
  69. {backlogops-0.9 → backlogops-1.1}/src/backlogops/work_hours.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: backlogops
3
- Version: 0.9
3
+ Version: 1.1
4
4
  Summary: Library with backlog operations.
5
5
  Author: Tom Björkholm
6
6
  Author-email: Tom Björkholm <klausuler_linnet0q@icloud.com>
@@ -22,13 +22,13 @@ Requires-Python: >=3.12
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE.txt
24
24
  Requires-Dist: config-as-json>=1.5
25
- Requires-Dist: cryptography>=50.0.0
25
+ Requires-Dist: cryptography>=50.0.1
26
26
  Requires-Dist: jira[cli,opt]>=3.10.5
27
27
  Requires-Dist: tableio>=1.1
28
- Requires-Dist: tableio-cfg-json>=1.2
28
+ Requires-Dist: tableio-cfg-json>=1.3
29
29
  Requires-Dist: wizard-ui-bridge[textual]>=1.3
30
- Requires-Dist: edit-cfg-json==0.0.2
31
30
  Requires-Dist: versionreporter>=0.4
31
+ Requires-Dist: edit-cfg-json>=0.1.0
32
32
  Dynamic: author
33
33
  Dynamic: license-file
34
34
  Dynamic: requires-dist
@@ -271,15 +271,12 @@ from backlogops import (
271
271
 
272
272
  ### Editing a configuration
273
273
 
274
- - `edit_model_for`, `default_edit_config`: build the `edit-cfg-json` model
275
- of an editing session over a `BacklogOpsConfig` or a stand-alone input
276
- or output preset, for the Tkinter editor of the graphical interface and
277
- the Textual editor of the command line.
278
-
279
274
  - `CONFIG_DESCRIPTIONS`, `INPUT_DESCRIPTIONS`, `OUTPUT_DESCRIPTIONS`:
280
275
  what each configuration member is for, shown below it in the editor.
281
276
  A member has no docstring at runtime, so this is what the editor is
282
277
  told; `descriptions_for` picks the mapping of one configuration class.
278
+ The nested TableIO endpoint is described by `tableio-cfg-json`, which
279
+ is asked for that text rather than having it repeated here.
283
280
 
284
281
  - `EDIT_SETTINGS`: what the editor may do to a configuration file of this
285
282
  application, including keeping what it writes over as a `.bak` file.
@@ -328,10 +325,10 @@ For the full set of public names see the API documentation linked above.
328
325
 
329
326
  ## Test summary
330
327
 
331
- - Test result: 2232 passed, 1 deselected in 44s
328
+ - Test result: 2298 passed, 1 deselected in 57s
332
329
  - No flake8 warnings.
333
330
  - No mypy errors found.
334
331
  - No pylint warnings.
335
332
  - No python layout warnings.
336
- - Built version(s): 0.9
333
+ - Built version(s): 1.1
337
334
  - Build and test using Python 3.14.7
@@ -235,15 +235,12 @@ from backlogops import (
235
235
 
236
236
  ### Editing a configuration
237
237
 
238
- - `edit_model_for`, `default_edit_config`: build the `edit-cfg-json` model
239
- of an editing session over a `BacklogOpsConfig` or a stand-alone input
240
- or output preset, for the Tkinter editor of the graphical interface and
241
- the Textual editor of the command line.
242
-
243
238
  - `CONFIG_DESCRIPTIONS`, `INPUT_DESCRIPTIONS`, `OUTPUT_DESCRIPTIONS`:
244
239
  what each configuration member is for, shown below it in the editor.
245
240
  A member has no docstring at runtime, so this is what the editor is
246
241
  told; `descriptions_for` picks the mapping of one configuration class.
242
+ The nested TableIO endpoint is described by `tableio-cfg-json`, which
243
+ is asked for that text rather than having it repeated here.
247
244
 
248
245
  - `EDIT_SETTINGS`: what the editor may do to a configuration file of this
249
246
  application, including keeping what it writes over as a `.bak` file.
@@ -292,10 +289,10 @@ For the full set of public names see the API documentation linked above.
292
289
 
293
290
  ## Test summary
294
291
 
295
- - Test result: 2232 passed, 1 deselected in 44s
292
+ - Test result: 2298 passed, 1 deselected in 57s
296
293
  - No flake8 warnings.
297
294
  - No mypy errors found.
298
295
  - No pylint warnings.
299
296
  - No python layout warnings.
300
- - Built version(s): 0.9
297
+ - Built version(s): 1.1
301
298
  - Build and test using Python 3.14.7
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: backlogops
3
- Version: 0.9
3
+ Version: 1.1
4
4
  Summary: Library with backlog operations.
5
5
  Author: Tom Björkholm
6
6
  Author-email: Tom Björkholm <klausuler_linnet0q@icloud.com>
@@ -22,13 +22,13 @@ Requires-Python: >=3.12
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE.txt
24
24
  Requires-Dist: config-as-json>=1.5
25
- Requires-Dist: cryptography>=50.0.0
25
+ Requires-Dist: cryptography>=50.0.1
26
26
  Requires-Dist: jira[cli,opt]>=3.10.5
27
27
  Requires-Dist: tableio>=1.1
28
- Requires-Dist: tableio-cfg-json>=1.2
28
+ Requires-Dist: tableio-cfg-json>=1.3
29
29
  Requires-Dist: wizard-ui-bridge[textual]>=1.3
30
- Requires-Dist: edit-cfg-json==0.0.2
31
30
  Requires-Dist: versionreporter>=0.4
31
+ Requires-Dist: edit-cfg-json>=0.1.0
32
32
  Dynamic: author
33
33
  Dynamic: license-file
34
34
  Dynamic: requires-dist
@@ -271,15 +271,12 @@ from backlogops import (
271
271
 
272
272
  ### Editing a configuration
273
273
 
274
- - `edit_model_for`, `default_edit_config`: build the `edit-cfg-json` model
275
- of an editing session over a `BacklogOpsConfig` or a stand-alone input
276
- or output preset, for the Tkinter editor of the graphical interface and
277
- the Textual editor of the command line.
278
-
279
274
  - `CONFIG_DESCRIPTIONS`, `INPUT_DESCRIPTIONS`, `OUTPUT_DESCRIPTIONS`:
280
275
  what each configuration member is for, shown below it in the editor.
281
276
  A member has no docstring at runtime, so this is what the editor is
282
277
  told; `descriptions_for` picks the mapping of one configuration class.
278
+ The nested TableIO endpoint is described by `tableio-cfg-json`, which
279
+ is asked for that text rather than having it repeated here.
283
280
 
284
281
  - `EDIT_SETTINGS`: what the editor may do to a configuration file of this
285
282
  application, including keeping what it writes over as a `.bak` file.
@@ -328,10 +325,10 @@ For the full set of public names see the API documentation linked above.
328
325
 
329
326
  ## Test summary
330
327
 
331
- - Test result: 2232 passed, 1 deselected in 44s
328
+ - Test result: 2298 passed, 1 deselected in 57s
332
329
  - No flake8 warnings.
333
330
  - No mypy errors found.
334
331
  - No pylint warnings.
335
332
  - No python layout warnings.
336
- - Built version(s): 0.9
333
+ - Built version(s): 1.1
337
334
  - Build and test using Python 3.14.7
@@ -1,8 +1,8 @@
1
1
  config-as-json>=1.5
2
- cryptography>=50.0.0
2
+ cryptography>=50.0.1
3
3
  jira[cli,opt]>=3.10.5
4
4
  tableio>=1.1
5
- tableio-cfg-json>=1.2
5
+ tableio-cfg-json>=1.3
6
6
  wizard-ui-bridge[textual]>=1.3
7
- edit-cfg-json==0.0.2
8
7
  versionreporter>=0.4
8
+ edit-cfg-json>=0.1.0
@@ -5,7 +5,7 @@ from setuptools import setup
5
5
 
6
6
  setup(
7
7
  name='backlogops',
8
- version='0.9',
8
+ version='1.1',
9
9
  description='Library with backlog operations.',
10
10
  author='Tom Björkholm',
11
11
  author_email='klausuler_linnet0q@icloud.com',
@@ -15,12 +15,12 @@ setup(
15
15
  package_data={'backlogops': ['src/py.typed']},
16
16
  install_requires=[
17
17
  'config-as-json >= 1.5',
18
- 'cryptography >= 50.0.0',
18
+ 'cryptography >= 50.0.1',
19
19
  'jira[cli,opt] >= 3.10.5',
20
20
  'tableio >= 1.1',
21
- 'tableio-cfg-json >= 1.2',
21
+ 'tableio-cfg-json >= 1.3',
22
22
  'wizard-ui-bridge[textual] >= 1.3',
23
- 'edit-cfg-json == 0.0.2',
24
- 'versionreporter >= 0.4'
23
+ 'versionreporter >= 0.4',
24
+ 'edit-cfg-json >= 0.1.0',
25
25
  ]
26
26
  )
@@ -32,8 +32,7 @@ from backlogops.backlog_releases_io import (
32
32
  from backlogops.config_descriptions import (
33
33
  CONFIG_DESCRIPTIONS, GUI_DESCRIPTIONS, INPUT_DESCRIPTIONS,
34
34
  JIRA_DESCRIPTIONS, OUTPUT_DESCRIPTIONS, WORKFORCE_DESCRIPTIONS)
35
- from backlogops.config_editing import (
36
- EDIT_SETTINGS, default_edit_config, descriptions_for, edit_model_for)
35
+ from backlogops.config_editing import EDIT_SETTINGS, descriptions_for
37
36
  from backlogops.config_file_io import (
38
37
  CONFIG_EXTENSION, io_preset_class, read_io_preset, safe_write_config)
39
38
  from backlogops.date_ranges import check_date_range, check_no_overlap
@@ -141,9 +140,9 @@ __all__ = [
141
140
  'apply_jira_keys', 'available_teams_wizard', 'backlog_in_release_order',
142
141
  'backlog_ops_wizard', 'build_backlog_releases', 'build_dependency_graph',
143
142
  'check_backlog_consistency', 'check_date_range',
144
- 'check_levels_consistency', 'check_no_overlap', 'default_edit_config',
143
+ 'check_levels_consistency', 'check_no_overlap',
145
144
  'default_jira_filter', 'descriptions_for',
146
- 'display_level_order', 'display_level_rows', 'edit_model_for',
145
+ 'display_level_order', 'display_level_rows',
147
146
  'encrypt_token_file',
148
147
  'encrypt_token_to_file', 'estimate_ready_date', 'estimate_release_dates',
149
148
  'event_finish', 'event_start', 'find_cycle', 'fold_level_name',
@@ -17,7 +17,10 @@ item levels:
17
17
  :class:`backlogops.jira_io_config.JiraIOConfig`;
18
18
  * ``levels`` is the optional list of backlog item levels. It is omitted
19
19
  from the file while it is ``None``; :meth:`BacklogOpsConfig.get_levels`
20
- then falls back to :data:`backlogops.levels.DEFAULT_LEVELS`.
20
+ then falls back to :data:`backlogops.levels.DEFAULT_LEVELS`. An empty
21
+ list is refused, because it would mean no levels at all where the
22
+ omitted member means the built-in ones, and the configuration editor
23
+ offers giving that member an empty list.
21
24
 
22
25
  Earlier file versions stored the workforce members (``persons``,
23
26
  ``teams`` and ``company_work_hours``) at the top level next to the
@@ -96,6 +99,12 @@ def _level_from_obj(name: str, value: object, stderr_file: TextIO) -> Level:
96
99
  return _level_from_dict(value, stderr_file)
97
100
 
98
101
 
102
+ _NO_LEVELS = ('no levels at all; a configuration that leaves the levels out '
103
+ 'uses the built-in ones, so take the member away rather than '
104
+ 'emptying it')
105
+ """Why an empty list of levels is not a configuration with no levels."""
106
+
107
+
99
108
  # pylint: disable-next=too-few-public-methods
100
109
  class _LevelsMember(MemberValidator):
101
110
  """Convert the optional ``levels`` member into a list of ``Level``."""
@@ -104,13 +113,23 @@ class _LevelsMember(MemberValidator):
104
113
  def validate_member(self, config: Config, member_name: str,
105
114
  member_value: object,
106
115
  stderr_file: TextIO = sys.stderr) -> Optional[object]:
107
- """Return the levels as a list of ``Level``, or ``None``."""
116
+ """Return the levels as a list of ``Level``, or ``None``.
117
+
118
+ Raises:
119
+ TypeError: The member holds something that is no list of
120
+ levels.
121
+ ValueError: The member holds an empty list, which is not a
122
+ configuration of the levels; leaving the member out is.
123
+ """
108
124
  _ = config
109
125
  if member_value is None:
110
126
  return None
111
127
  if not isinstance(member_value, list):
112
128
  report_wrong_type(member_name, member_value, list, stderr_file,
113
129
  'BacklogOps config')
130
+ if not member_value:
131
+ report_bad_value(member_name, member_value, _NO_LEVELS,
132
+ stderr_file, 'BacklogOps config')
114
133
  return [_level_from_obj(f'{member_name}[{index}]', element,
115
134
  stderr_file)
116
135
  for index, element in enumerate(member_value)]
@@ -40,11 +40,15 @@ more names than fit in one such line has the meaning of them said about the
40
40
  member instead, which is why ``token_storage`` explains its modes here and
41
41
  ``level_display`` does not.
42
42
 
43
- Two more things are deliberately left out. The nested TableIO endpoint is
44
- described by ``tableio_cfg_json``, which owns those members and documents
45
- them itself, so only the member holding it is described here. And a limit
46
- that lives inside a validator is not read by the editor and is stated in
47
- words where it matters.
43
+ The nested TableIO endpoint is described by ``tableio_cfg_json``, which
44
+ owns those members: :func:`tio_json_descriptions` is asked for their text
45
+ under the path of the member holding the endpoint, so the TableIO
46
+ documentation is neither repeated here nor able to drift from it. What is
47
+ written here about that endpoint is the one line about the member itself.
48
+
49
+ One more thing is deliberately left out: a limit that lives inside a
50
+ validator is not read by the editor and is stated in words where it
51
+ matters.
48
52
  """
49
53
 
50
54
  # Copyright (c) 2026, Tom Björkholm
@@ -52,6 +56,7 @@ words where it matters.
52
56
 
53
57
  from config_as_json import ConfigPath
54
58
  from edit_cfg_json import Descriptions
59
+ from tableio_cfg_json import tio_json_descriptions
55
60
 
56
61
  EVERY = '['
57
62
  """The path step meaning every element of a list or every value of a dict.
@@ -169,12 +174,17 @@ WORKFORCE_DESCRIPTIONS: Descriptions = {
169
174
  _FTE_EXCEPTION)}
170
175
  """What every member of an ``AvailableTeamsConfig`` is for."""
171
176
 
172
- _TABLEIO_MEMBER = ('How the file itself is read or written: its format, and '
173
- 'the settings of that format. These are the members of '
174
- 'tableio-cfg-json, which documents them; a setting that '
175
- 'is not in the file keeps its default and has no line '
176
- 'here.')
177
- """What the nested TableIO endpoint of one preset is for."""
177
+ _TABLEIO: Descriptions = {
178
+ ('tableio',): 'How the file itself is read or written: its format, and '
179
+ 'the settings of that format. A setting that is not in '
180
+ 'the file keeps its default and has no line here.',
181
+ **tio_json_descriptions(('tableio',))}
182
+ """What the nested TableIO endpoint of one preset and its settings are for.
183
+
184
+ Only the line about the member itself is written here. Everything below it
185
+ belongs to ``tableio_cfg_json``, which is asked for it, so the formats,
186
+ implementations and values named are the ones registered now.
187
+ """
178
188
 
179
189
  _MAPPED_COLUMN = ('Empty drops the column altogether. A column that is not '
180
190
  'named here keeps its own name.')
@@ -198,7 +208,7 @@ INPUT_DESCRIPTIONS: Descriptions = {
198
208
  'alone.',
199
209
  ('status_input_map', EVERY): 'Internal status this name is read as: one '
200
210
  'of TODO, IN_PROGRESS, DONE or REJECTED.',
201
- ('tableio',): _TABLEIO_MEMBER}
211
+ **_TABLEIO}
202
212
  """What every member of an ``InputFormatConfig`` is for."""
203
213
 
204
214
 
@@ -231,7 +241,7 @@ def _display_members(action: str) -> Descriptions:
231
241
 
232
242
 
233
243
  OUTPUT_DESCRIPTIONS: Descriptions = {**_display_members('written'),
234
- ('tableio',): _TABLEIO_MEMBER}
244
+ **_TABLEIO}
235
245
  """What every member of an ``OutputFormatConfig`` is for."""
236
246
 
237
247
  GUI_DESCRIPTIONS: Descriptions = _display_members('shown')
@@ -0,0 +1,110 @@
1
+ #! /usr/local/bin/python3
2
+ """What a configuration editor of a UI is told about this application.
3
+
4
+ The wizard asks one question after another, which is what building a
5
+ configuration from nothing wants. Editing an existing one wants the other
6
+ shape: the whole configuration on the screen at once, folded where it is
7
+ deep, so a single value can be changed without walking past everything
8
+ else. ``edit_cfg_json`` is that editor, and this module is the two answers
9
+ it asks the application for, so that the command line and the graphical
10
+ interface show one configuration the same way.
11
+
12
+ The first answer is :func:`descriptions_for`, which is what each editable
13
+ class says about its own members. Which class is edited is the class of the
14
+ configuration object the editor is given, so the caller decides whether a
15
+ complete backlog-ops configuration or a stand-alone preset is being edited,
16
+ and the descriptions of that class follow from it. A caller that has a
17
+ class rather than an object, such as a command line naming the kind of file
18
+ it edits, gets the object from ``edit_cfg_json.default_config``.
19
+
20
+ The second answer is :data:`EDIT_SETTINGS`, which is what the editor may do
21
+ to a file. Saving is the editor's own: it validates the whole configuration
22
+ through the configuration class and only then writes, keeping what it wrote
23
+ over as a ``.bak`` file, because an editor overwrites the file it read.
24
+
25
+ Nothing here opens an editor. A user interface that already runs its own
26
+ toolkit mounts the editor itself — ``edit_cfg_json_tk.TkEditorPanel`` in a
27
+ window of the application, ``edit_cfg_json_textual.edit`` in a terminal of
28
+ its own — and hands those two answers to it.
29
+
30
+ How many things a member holds is the editor's to change as well, so a
31
+ named preset, a Jira connection, a person, a team, a membership, an
32
+ exception, a level and an entry of any of the maps can each be added beside
33
+ the ones that are there, taken out again, and moved within a list. A new
34
+ element is never invented: it is an object of the class the declaration
35
+ names, a copy of what the container holds now, or the empty value that the
36
+ declared type of the member says an element is. That is what the first of
37
+ the notes below follows from.
38
+
39
+ Four things are worth knowing before the editor is offered instead of the
40
+ wizard:
41
+
42
+ * One entry of the Jira column maps and of the issue-type maps is a map of
43
+ its own, which the class does not name a type for, so there is no pattern
44
+ for one until the file holds one to copy: an empty map of those offers
45
+ nothing and says why below itself. Every other container can be given its
46
+ first element, because the class names the type of one or its declared
47
+ type says what an element is.
48
+ * The levels keep a row in a configuration that leaves them out, because a
49
+ member the class omits is still a member. What that row offers is an
50
+ empty list, which is not what leaving the levels out means and is
51
+ refused as such: the row's other control puts the member back to holding
52
+ nothing, which is the built-in levels. Writing the levels the first time
53
+ is the wizard's.
54
+ * A person is keyed by their own name in lower case, so renaming one means
55
+ adding an entry under the new key and removing the old one; editing the
56
+ name alone leaves a configuration the class refuses.
57
+ * The editor reads the declarations of a class and not the insides of its
58
+ validators, so it offers a change that this library then refuses where
59
+ only a validator knows better: a week day taken out of the company
60
+ schedule, or a level that loses its name, because a level is a plain
61
+ object in the file rather than a nested configuration class. What the
62
+ class said is shown at the member it is about.
63
+ """
64
+
65
+ # Copyright (c) 2026, Tom Björkholm
66
+ # MIT License
67
+
68
+ from typing import Optional
69
+ from config_as_json import Config
70
+ from edit_cfg_json import Descriptions, Settings
71
+ from backlogops.backlog_ops_config import BacklogOpsConfig
72
+ from backlogops.config_descriptions import CONFIG_DESCRIPTIONS, \
73
+ INPUT_DESCRIPTIONS, OUTPUT_DESCRIPTIONS
74
+ from backlogops.config_file_io import CONFIG_EXTENSION
75
+ from backlogops.io_config import InputFormatConfig, OutputFormatConfig
76
+
77
+ EDIT_SETTINGS = Settings(file_extension=CONFIG_EXTENSION,
78
+ extension_enforced=False, backup_suffix='.bak',
79
+ backup_count=1, confirm_overwrite=True)
80
+ """What the editor may do to a configuration file of this application.
81
+
82
+ The extension is added to a destination that has none, and a name with
83
+ another extension is accepted, which is how the wizard commands complete a
84
+ file name too. Overwriting a file this session has not written is confirmed
85
+ first and the previous content is then kept as that name plus ``.bak``,
86
+ because the editor writes over the file it read and that file may hold a
87
+ configuration somebody else wrote.
88
+
89
+ The default key combinations apply: none of them is taken by the
90
+ application around the editor.
91
+ """
92
+
93
+ CLASS_DESCRIPTIONS: dict[type[Config], Descriptions] = {
94
+ BacklogOpsConfig: CONFIG_DESCRIPTIONS,
95
+ InputFormatConfig: INPUT_DESCRIPTIONS,
96
+ OutputFormatConfig: OUTPUT_DESCRIPTIONS}
97
+ """What each editable configuration class says about its own members."""
98
+
99
+
100
+ def descriptions_for(config: Config) -> Optional[Descriptions]:
101
+ """Return what the class of one configuration says about its members.
102
+
103
+ Args:
104
+ config: The configuration object that is to be edited.
105
+
106
+ Returns:
107
+ The descriptions of that class, or None for a class this library
108
+ says nothing about, which the editor shows without descriptions.
109
+ """
110
+ return CLASS_DESCRIPTIONS.get(type(config))
@@ -1,149 +0,0 @@
1
- #! /usr/local/bin/python3
2
- """Build the edit model that a configuration editor of a UI shows.
3
-
4
- The wizard asks one question after another, which is what building a
5
- configuration from nothing wants. Editing an existing one wants the other
6
- shape: the whole configuration on the screen at once, folded where it is
7
- deep, so a single value can be changed without walking past everything
8
- else. ``edit_cfg_json`` is that editor, and this module is everything of it
9
- that is not a widget, so that the command line and the graphical interface
10
- show one configuration the same way.
11
-
12
- :func:`edit_model_for` reads the input file and returns the model to show.
13
- Which class is edited is the class of the configuration object it is given,
14
- so the caller decides whether a complete backlog-ops configuration or a
15
- stand-alone preset is being edited, and the descriptions of that class
16
- follow from it. A caller that has a class rather than an object, such as a
17
- command line naming the kind of file it edits, gets the object from
18
- :func:`default_edit_config`.
19
-
20
- What the editor may do to a file is :data:`EDIT_SETTINGS`. Saving is the
21
- editor's own: it validates the whole configuration through the
22
- configuration class and only then writes, keeping what it wrote over as a
23
- ``.bak`` file, because an editor overwrites the file it read.
24
-
25
- Three things the editor cannot do are worth knowing before it is offered
26
- instead of the wizard, and all three are of ``edit_cfg_json`` itself rather
27
- than of this configuration:
28
-
29
- * A dict whose keys the application validates for itself cannot gain or
30
- lose a key. That is the status map, the column-name maps of a preset and
31
- of the display, and the Jira column and issue-type maps: their values are
32
- editable, and a new entry is the wizard's to create.
33
- * A member left out of the file has no row, so the levels can be edited
34
- only in a configuration that already states them.
35
- * A person is keyed by their own name in lower case, so renaming one means
36
- adding an entry under the new key and removing the old one; editing the
37
- name alone leaves a configuration the class refuses.
38
- """
39
-
40
- # Copyright (c) 2026, Tom Björkholm
41
- # MIT License
42
-
43
- import sys
44
- from typing import Optional, TextIO
45
- from config_as_json import Config, PathOrStr
46
- from edit_cfg_json import ConfigLoadError, Descriptions, EditModel, Settings, \
47
- default_config, load_config
48
- from backlogops.backlog_ops_config import BacklogOpsConfig
49
- from backlogops.config_descriptions import CONFIG_DESCRIPTIONS, \
50
- INPUT_DESCRIPTIONS, OUTPUT_DESCRIPTIONS
51
- from backlogops.config_file_io import CONFIG_EXTENSION
52
- from backlogops.io_config import InputFormatConfig, OutputFormatConfig
53
-
54
- EDIT_SETTINGS = Settings(file_extension=CONFIG_EXTENSION,
55
- extension_enforced=False, backup_suffix='.bak',
56
- backup_count=1, confirm_overwrite=True)
57
- """What the editor may do to a configuration file of this application.
58
-
59
- The extension is added to a destination that has none, and a name with
60
- another extension is accepted, which is how the wizard commands complete a
61
- file name too. Overwriting a file this session has not written is confirmed
62
- first and the previous content is then kept as that name plus ``.bak``,
63
- because the editor writes over the file it read and that file may hold a
64
- configuration somebody else wrote.
65
-
66
- The default key combinations apply: none of them is taken by the
67
- application around the editor.
68
- """
69
-
70
- CLASS_DESCRIPTIONS: dict[type[Config], Descriptions] = {
71
- BacklogOpsConfig: CONFIG_DESCRIPTIONS,
72
- InputFormatConfig: INPUT_DESCRIPTIONS,
73
- OutputFormatConfig: OUTPUT_DESCRIPTIONS}
74
- """What each editable configuration class says about its own members."""
75
-
76
-
77
- def descriptions_for(config: Config) -> Optional[Descriptions]:
78
- """Return what the class of one configuration says about its members.
79
-
80
- Args:
81
- config: The configuration object that is to be edited.
82
-
83
- Returns:
84
- The descriptions of that class, or None for a class this library
85
- says nothing about, which the editor shows without descriptions.
86
- """
87
- return CLASS_DESCRIPTIONS.get(type(config))
88
-
89
-
90
- def default_edit_config(config_type: type[Config]) -> Config:
91
- """Return a configuration holding the declared defaults of one class.
92
-
93
- It is the door for a caller that has a class rather than an object,
94
- which is what a command line naming the kind of file it edits has.
95
-
96
- Args:
97
- config_type: The configuration class to be edited.
98
-
99
- Returns:
100
- A configuration object holding only what that class declares.
101
-
102
- Raises:
103
- ValueError: The editor cannot construct that class on its own.
104
- """
105
- try:
106
- return default_config(config_type)
107
- except ConfigLoadError as error:
108
- raise ValueError(str(error)) from error
109
-
110
-
111
- def edit_model_for(config: Config, *, in_file: Optional[PathOrStr] = None,
112
- out_file: Optional[PathOrStr] = None,
113
- stderr_file: TextIO = sys.stderr) -> EditModel:
114
- """Read the configuration to edit and return the model of a session.
115
-
116
- The class of ``config`` decides which class is edited and supplies the
117
- values a member the input file leaves out falls back to. The object
118
- itself is never modified: what a save wrote is
119
- ``EditModel.saved_config``.
120
-
121
- Args:
122
- config: Configuration object of the class to edit, holding the
123
- values to start from when there is no input file.
124
- in_file: Configuration file to read, or None to edit the values
125
- that ``config`` holds.
126
- out_file: Configuration file a save writes, or None to write the
127
- input file. With neither, the editor asks the user for one
128
- before it can save. A destination named here is one this
129
- session chose, so it is given the configuration extension when
130
- it has none; the input file is inherited and taken as it is.
131
- stderr_file: Stream used for user-facing diagnostics.
132
-
133
- Returns:
134
- The model of one editing session, for a UI backend to show.
135
-
136
- Raises:
137
- ValueError: The input file cannot be opened for editing. The
138
- message holds what the configuration class said about it.
139
- """
140
- try:
141
- loaded = load_config(config, in_file=in_file, settings=EDIT_SETTINGS)
142
- except ConfigLoadError as error:
143
- raise ValueError(str(error)) from error
144
- model = EditModel(loaded.config, loaded.report,
145
- descriptions=descriptions_for(config), out_file=in_file,
146
- settings=EDIT_SETTINGS, stderr_file=stderr_file)
147
- if out_file is not None:
148
- model.set_out_file(out_file)
149
- return model
File without changes
File without changes
File without changes