edit-cfg-json-tk 0.0.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tom Björkholm
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,535 @@
1
+ Metadata-Version: 2.4
2
+ Name: edit-cfg-json-tk
3
+ Version: 0.0.2
4
+ Summary: Library for editing config-as-json with Tkinter.
5
+ Author: Tom Björkholm
6
+ Author-email: Tom Björkholm <klausuler_linnet0q@icloud.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/tom-bjorkholm/edit-cfg-json
9
+ Project-URL: Source code, https://github.com/tom-bjorkholm/edit-cfg-json
10
+ Project-URL: Documentation, https://github.com/tom-bjorkholm/edit-cfg-json/blob/master/doc/
11
+ Keywords: edit,configuration,JSON,validation,Tk,Tkinter
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Development Status :: 3 - Alpha
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.12
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE.txt
24
+ Requires-Dist: edit-cfg-json==0.0.*,>=0.0.2
25
+ Requires-Dist: argcomplete>=3.7.2
26
+ Requires-Dist: wizard-tk-bridge>=1.3
27
+ Requires-Dist: wizard-ui-bridge>=1.3
28
+ Dynamic: author
29
+ Dynamic: license-file
30
+ Dynamic: requires-dist
31
+ Dynamic: requires-python
32
+
33
+ # edit-cfg-json-tk
34
+
35
+ There are 3 related packages for editing a `config-as-json`
36
+ configuration:
37
+
38
+ - **[edit-cfg-json-tk](https://pypi.org/project/edit-cfg-json-tk/)** a
39
+ desktop editor based on Tkinter. It is a thin backend on top of the
40
+ core.
41
+
42
+ - **[edit-cfg-json-textual](https://pypi.org/project/edit-cfg-json-textual/)**
43
+ a terminal editor based on Textual. It is a thin backend on top of the
44
+ core.
45
+
46
+ - **[edit-cfg-json](https://pypi.org/project/edit-cfg-json/)** the user
47
+ interface agnostic core. It discovers the editable structure of a
48
+ `config_as_json.Config` object by introspection, and owns all editing,
49
+ validation and file handling. It is also the package a third party
50
+ writes a new user interface backend against. The only backend it ships
51
+ itself is a very limited non-interactive one that prints the model once
52
+ and returns, for a script, a test or a continuous integration job.
53
+
54
+ The application supplies its own `Config` object and gets a folding
55
+ editor for it, without writing any user interface code and without
56
+ describing its configuration schema a second time.
57
+
58
+ The three packages share a version number and are released together. The
59
+ first two are the editors: pick the one that matches how your application
60
+ is used, and it pulls in the core itself.
61
+
62
+ ## Project status
63
+
64
+ **Alpha. No API stability and no backward compatibility is offered while
65
+ this package is in Alpha.** That applies to the core and to both
66
+ backends. Public names may change without a major version bump.
67
+
68
+ Semantic versioning starts when the Alpha period ends. Until then, pin an
69
+ exact version if your build needs to be reproducible.
70
+
71
+ ## What this package does
72
+
73
+ `edit-cfg-json-tk` is the Tkinter desktop editor. It is a thin backend on
74
+ top of `edit-cfg-json`, which it installs as a dependency. All the
75
+ editing, validation and file handling logic lives in the core; this
76
+ package only draws it and forwards the user's actions.
77
+
78
+ Tkinter itself is not installable from PyPI. It comes with most Python
79
+ distributions, but on some Linux distributions it is a separate system
80
+ package, such as `python3-tk`.
81
+
82
+ ## Main entry points
83
+
84
+ Everything a user of this package needs is re-exported from the top-level
85
+ `edit_cfg_json_tk` package, so it can be imported directly:
86
+
87
+ ````python
88
+ from edit_cfg_json_tk import TkEditor, edit
89
+ ````
90
+
91
+ `edit` is the short way in for an application that has already chosen
92
+ Tkinter. It is `edit_cfg_json.edit` with this package's backend filled in,
93
+ and it gives back the configuration object that was saved, or `None` when
94
+ nothing was:
95
+
96
+ ````python
97
+ from edit_cfg_json_tk import edit
98
+
99
+ saved = edit(config=config, in_file='my_config.json')
100
+ ````
101
+
102
+ `TkEditor` is the Tkinter implementation of the `EditorBackend` protocol of
103
+ `edit-cfg-json`, for an application that builds the model itself. It has the
104
+ one method that protocol asks for:
105
+
106
+ ````python
107
+ from edit_cfg_json import EditModel, load_config
108
+ from edit_cfg_json_tk import TkEditor
109
+
110
+ loaded = load_config(config=config, in_file='my_config.json')
111
+ model = EditModel(config=loaded.config, report=loaded.report,
112
+ out_file='my_config.json')
113
+ TkEditor().run_editor(model)
114
+ saved = model.saved_config
115
+ ````
116
+
117
+ ## The edit-cfg-json-tk program
118
+
119
+ Installing this package also installs a program of the same name, so an
120
+ application author gets a Tk editor for their own configuration class without
121
+ writing a line of code:
122
+
123
+ ````sh
124
+ edit-cfg-json-tk --module myapp.config --class AppConfig -i /etc/myapp.json
125
+ ````
126
+
127
+ The window it opens is the one this page describes, on the class that was
128
+ named. It is `edit_cfg_json.run_cli` with this package's backend filled in, so
129
+ the command line below is the same one that `edit-cfg-json-textual` has; what
130
+ differs is which of the two shows the configuration.
131
+
132
+ ### Telling it which class to edit
133
+
134
+ The class is told and never guessed. `--module` names a module that is
135
+ importable, `--file` names a Python file that is not, exactly one of the two is
136
+ required, and `--class` names the class in it:
137
+
138
+ ````sh
139
+ edit-cfg-json-tk --module myapp.config --class AppConfig -i /etc/myapp.json
140
+ edit-cfg-json-tk --file ./somewhere/cfg.py --class AppConfig
141
+ ````
142
+
143
+ `--module` uses the ordinary import path, so `PYTHONPATH` reaches a package
144
+ that is not installed. `--file` puts the folder of the file at the front of the
145
+ path and imports the file by its own name, so a file that imports its
146
+ neighbours works — but a file that belongs to a package and uses a relative
147
+ import cannot be loaded from a bare path at all, and is refused with a message
148
+ saying to use `--module` with `PYTHONPATH` instead.
149
+
150
+ **Importing a module runs it.** That is the same exposure as running the file
151
+ with `python`, and it is not guarded against, because a configuration class is
152
+ Python and reaching it means importing the module it is in.
153
+
154
+ ### The rest of the command line
155
+
156
+ | Option | Meaning |
157
+ | --- | --- |
158
+ | `-i`, `--input` | Configuration file to read. Without it the editor starts from the values the class declares. |
159
+ | `-o`, `--output` | Configuration file to write. Without it the input file is written, which is what an editor is normally asked to do. |
160
+ | `--policy` | What to do about a declared value the file does not hold: `strict-then-defaults`, which is the default, `strict` or `defaults`. |
161
+ | `--descriptions` | Name of an `edit_cfg_json.Descriptions` mapping beside the class, saying what its members are for. Without it the members are shown with whatever their own types say about them, which for most of them is nothing. |
162
+
163
+ A member has no docstring at runtime, so what a member is for is either in a
164
+ mapping like that or nowhere at all, which is why `--descriptions` exists: it is
165
+ the one thing an application knows that this program could not otherwise pass
166
+ on. The docstring of the configuration class needs no option, because the class
167
+ carries it.
168
+
169
+ An application that has more to say about its own configuration — the file name
170
+ extension it uses, the key combinations its own user interface has taken, and
171
+ what becomes of a file that a save writes over — says it in
172
+ `edit_cfg_json.Settings`, and gets there through `edit` rather than through this
173
+ program. Options for those are what a later version of this program adds.
174
+
175
+ ### A class this editor cannot construct on its own
176
+
177
+ Most configuration classes take the keyword arguments that `config_as_json`
178
+ documents and nothing else, and this program constructs them from the signature
179
+ it reads. A class that needs an argument of the application's own — a folder, a
180
+ connection, the list of names its own validators accept — is reached through
181
+ `--loader NAME` instead, which names an `edit_cfg_json.ConfigLoader` in the same
182
+ module or file:
183
+
184
+ ````sh
185
+ edit-cfg-json-tk --module myapp.config --loader make_config -i /etc/myapp.json
186
+ ````
187
+
188
+ Whatever the loader needs beyond the four keyword arguments of that protocol has
189
+ to be bound where the loader is written, for instance with
190
+ `functools.partial`, because a command line cannot supply an argument this
191
+ library knows nothing about. `edit_cfg_json.derived_loader` is one line for the
192
+ ordinary case:
193
+
194
+ ````python
195
+ make_config = derived_loader(partial(AppConfig, known_teams=TEAMS))
196
+ ````
197
+
198
+ At least one of `--class` and `--loader` is needed and both are allowed. A
199
+ loader may choose its class by looking at the file it is given, and `--class`
200
+ beside it is then how a script says which class it is prepared to go on with:
201
+ the run stops with its own exit code if the loader answers with another one.
202
+
203
+ ### How the run ends
204
+
205
+ The program is meant to be usable from a script, so each way of refusing has an
206
+ exit code of its own:
207
+
208
+ | Code | What it means |
209
+ | --- | --- |
210
+ | `0` | Everything the program was asked to do was done. |
211
+ | `1` | The input file cannot be opened for editing. |
212
+ | `2` | The command line itself is wrong. |
213
+ | `3` | The module that `--module` names cannot be imported. |
214
+ | `4` | The file that `--file` names cannot be read. |
215
+ | `5` | That file is not Python that can be imported. |
216
+ | `6` | That file needs the package it belongs to. |
217
+ | `7` | The module holds no such name. |
218
+ | `8` | That name is not a class based on `config_as_json.Config`. |
219
+ | `9` | The editor cannot construct that class on its own. |
220
+ | `10` | The values are not ones the application would accept. |
221
+ | `11` | The output file was asked for and was not written. |
222
+ | `12` | The values of that class cannot be written as JSON, so there is nothing to show. |
223
+ | `13` | The name that `--loader` names cannot be called at all. |
224
+ | `14` | The loader needs arguments that a command line cannot supply. |
225
+ | `15` | The loader did not construct the class that `--class` asked for. |
226
+ | `16` | The name that `--descriptions` names is no mapping of any kind. |
227
+
228
+ The numbers are `edit_cfg_json.ExitCode`, so a program that runs this one can
229
+ name them instead of writing them out.
230
+
231
+ Codes `10` and `11` are never answered by this program. They belong to a run
232
+ whose backend prints once and returns, which is the
233
+ `python3 -m edit_cfg_json.dump` utility of the core package.
234
+ A program that gave the user a session ends with success when the user closes
235
+ it, whatever is left in the fields, because closing an editor is not a failure.
236
+
237
+ ### If the script folder is not on the path
238
+
239
+ This program is also reachable through the package it belongs to, which needs
240
+ nothing to be on `PATH`:
241
+
242
+ ````sh
243
+ python3 -m edit_cfg_json_tk --module myapp.config --class AppConfig
244
+ ````
245
+
246
+ ### Completing the command line
247
+
248
+ The program completes its own options and file names with
249
+ [argcomplete](https://pypi.org/project/argcomplete), which is installed with
250
+ it. Register it once for your shell:
251
+
252
+ ````sh
253
+ eval "$(register-python-argcomplete edit-cfg-json-tk)"
254
+ ````
255
+
256
+ ## What the window shows
257
+
258
+ The window holds the label of the configuration, what the class says about
259
+ itself, what reading the input file did, and then one row per node of the
260
+ configuration. Below those, in a part of the window that does not scroll, are
261
+ the validation verdict, the saving line, and the buttons: Validate, Save,
262
+ Save as..., a tick-box for Explain, a button that folds or opens every
263
+ container, and Close. Every one of them has a key as well.
264
+
265
+ Every change of a field goes straight into the model, and the label above the
266
+ rows is marked while the model holds a change worth saving.
267
+
268
+ A field is shown with a background, a border and a caret colour of its own, so
269
+ that what can be typed into can be told from what only says something. Those
270
+ are stated rather than inherited: the window is white, so a field that kept the
271
+ background it was given could not be seen at all.
272
+
273
+ ### One row per node
274
+
275
+ A member that holds a list, a dict or a nested `config_as_json.Config` object
276
+ is not one field. It is a row of its own with the rows of what it holds
277
+ indented below it, a field at every value, and no field on the row of the
278
+ container itself — which says how many things it holds, or which class the
279
+ object at it is, where a value would be.
280
+
281
+ A container has a control at the left of its row, `-` while it is open and `+`
282
+ while it is folded, and pressing it hides or shows everything inside it. The
283
+ button below the rows does the same to all of them at once, and its text says
284
+ what the next press will do: `Fold all` while anything is open, `Unfold all`
285
+ once nothing is. A configuration with nothing to fold gets neither the button
286
+ nor the column that the controls sit in, so the values keep that width.
287
+
288
+ A nested configuration object shows its own docstring below its row and its own
289
+ members as the rows under that, in the order *its* class declares them. Folding
290
+ it leaves the first paragraph of that docstring, because an object showing less
291
+ of itself says less about itself.
292
+
293
+ ### What one nested object is on its own
294
+
295
+ Beside the class on the row of a nested object is what that object is when it is
296
+ asked about itself: *valid on its own* or *refused on its own*. A list or a dict
297
+ of such objects says what the objects in it amount to — *valid inside* or
298
+ *refused inside* — because its row is the only one that folding leaves on the
299
+ screen.
300
+
301
+ Folding a node asks every object at or inside it, and so does opening one, so
302
+ the badge appears as soon as a container is folded out of the way. A member that
303
+ one of those objects refused says why below itself, exactly as the verdict of
304
+ the whole configuration does; what an object refused about no member of itself
305
+ is said at the object.
306
+
307
+ The words that qualify the badge are the whole point. A rule of the class above
308
+ may relate two objects across the boundary between them, and then every object
309
+ is valid on its own while the configuration cannot be written. The verdict line
310
+ below the rows is the only thing that answers whether the file can be saved.
311
+
312
+ ### Changing how many things a member holds
313
+
314
+ At the end of the line of a node are the controls for its elements: `Add`,
315
+ `Del`, `Up` and `Down`, and only the ones that node really offers. They sit at
316
+ the end rather than in a column of their own, so a node that offers none of them
317
+ costs the values no width at all, which is what makes four of them affordable.
318
+
319
+ `Add` copies: a list or a dict whose class declares that its elements are
320
+ configuration objects gets one object of that class holding the values it
321
+ declares, and any other list gets a copy of the element the class declares for
322
+ it, or of the first element it holds now. Adding an entry to a dict opens a
323
+ small dialog for the key, because nothing but the person configuring the
324
+ application knows what a new entry is called; a key the dict already holds is
325
+ asked about again rather than allowed to take the place of what is there.
326
+
327
+ A container that cannot be given an element gets no `Add` at all, and says why
328
+ below itself instead — an ordinary dict member, for instance, because
329
+ `config_as_json` matches such a member against the keys its class declares, so
330
+ a dict that gained one would be refused by the configuration class itself. That
331
+ line is explanation rather than something to act on, so it is muted and the
332
+ Explain tick-box covers it.
333
+
334
+ ### Validating, saving and closing
335
+
336
+ Validate runs the validation of the application's own configuration class
337
+ and shows what that class would say about the values that are in the fields.
338
+ What it said about one node is shown **below that node**, and the line
339
+ below the rows names the nodes it was about, by the whole path to each of them,
340
+ so a configuration too tall for the window does not leave the user hunting for
341
+ the field. What the class said that is about no single node — a
342
+ whole-configuration rule, a key that does not match — stays in that line,
343
+ because there is no field it belongs to. Every refused node is marked at once,
344
+ and not only the first one, because the editor walks the validation plan itself
345
+ rather than stopping where `Config.validate()` stops.
346
+
347
+ A pass is not read only: a validator returns the value that is stored back
348
+ into the member, so the fields are written back from the model afterwards, and
349
+ a member that a validator rewrote says so beside its field. A pass can also
350
+ change how many rows there are — a validator that sorts a list and removes its
351
+ duplicates removes one — and the window then builds its rows again rather than
352
+ writing into a widget for a value that is no longer there.
353
+
354
+ **Leaving a field** asks a smaller question of that one member: whether what
355
+ was typed into it means a value of that member at all. It is the question a
356
+ `parse_converters()` entry answers, an enum being the case that arises in
357
+ practice, and it is asked when the field loses the focus rather than on every
358
+ key, because a name that is being typed is no name of a member for most of
359
+ the time it takes to type it.
360
+
361
+ Save writes the output file, and refuses to write values the application
362
+ would not accept: the diagnostics then say what is wrong with them and the
363
+ file on disk is left exactly as it was. Saving runs the same pass as Validate
364
+ does, so it can rewrite a value as well, and the fields show what really
365
+ reached the file. What was written is no longer waiting to be written, so the
366
+ mark above the rows goes away and the editor stays open.
367
+
368
+ Save as asks for the file with the ordinary system dialog. What that dialog
369
+ offers is what the application decided in its `edit_cfg_json.Settings`: the
370
+ extension it uses for its configuration is the one the dialog adds to a name
371
+ that has none, and the one it offers to filter by, and an application that
372
+ enforces its extension gets that filter and no other. An application with no
373
+ opinion gets a dialog with none, because this library has none of its own
374
+ about what a configuration file is called. Save asks the same question when
375
+ the session has no file to write yet, which is what every editor does.
376
+
377
+ **A save that would write over a file this session has not written asks
378
+ first**, in a dialog whose default answer is the one that leaves the file
379
+ alone. The previous content is then kept under the name the application chose,
380
+ and the saving line says where it went. Both the question and the name are the
381
+ core's, so this backend and the Textual one cannot treat the user's old
382
+ configuration differently. The system dialog is told not to ask about
383
+ overwriting itself, although it offers to: the question is asked once, and it is
384
+ asked by the editor.
385
+
386
+ Close writes nothing of its own. It is the "cancel" of the editor, and it is
387
+ called Close rather than Cancel because saving leaves the editor open: a
388
+ button called Cancel beside values that have already been written would read
389
+ as an offer to undo the writing, which it is not.
390
+
391
+ **Closing an editor that holds something unsaved asks whether the changes may
392
+ be dropped**, and the answer that keeps them is the one the dialog opens on.
393
+ The button, the key and the close button of the window all go through one place,
394
+ because the one way out that is not a widget of the editor would otherwise be
395
+ the one way out that drops the changes silently. Closing again after a Save asks
396
+ nothing, because a save leaves nothing to lose.
397
+
398
+ Explain shows or hides what the application says about these values: the
399
+ whole docstring of the configuration class above the rows, the docstring of
400
+ each nested object, the description of each described member below its own
401
+ field, what kind of value each member holds, and why a container cannot be
402
+ given an element. The editor opens with them shown, and what is left when they
403
+ are hidden is the first paragraph of the class docstring, because one line for
404
+ the whole configuration is worth keeping. A member the application described
405
+ gets a line and one it said nothing about gets none, rather than an empty one.
406
+ Which of the two states the editor is in belongs to the model, so this backend
407
+ and the Textual one cannot disagree about it.
408
+
409
+ It is a tick-box rather than a button, and the tick is what says which of the
410
+ two states the window is in: a button saying Explain beside explanations that
411
+ are already there would be offering something that has been done. The key of
412
+ the action moves the tick with it, because Tk moves it only when it was the
413
+ tick-box that was pressed.
414
+
415
+ What reading the input file did is shown above the rows, when it did
416
+ anything, because it is what explains the marks below it: a member that the
417
+ file did not hold says so beside its field, and so does one whose value the
418
+ reading of the file put there or altered — with the older key it was read from,
419
+ where the class recorded one. Both the message and the marks are read from the
420
+ model, so the two backends cannot tell the user two different things about one
421
+ file.
422
+
423
+ ## Scrolling, and the colours
424
+
425
+ The label, the docstring, the load message and the member rows are on a canvas
426
+ that scrolls, and the verdict, the saving line and the buttons are below it and
427
+ stay where they are: they are what a user reaches for after editing rather than
428
+ something to scroll to. The scrollbar is beside the canvas, and the mouse wheel
429
+ scrolls it however the platform reports one.
430
+
431
+ The window opens at the size the configuration asks for, up to the size of a
432
+ window, so a small configuration gets a small window and a large one is
433
+ scrolled through rather than cut off. A long list therefore does not decide the
434
+ size of the window twice: it opens folded when opening it would add more rows
435
+ than the editor opens at, and the window is the size of what is on the screen.
436
+
437
+ Every text that is a paragraph — the docstring, a description, a message, what
438
+ is wrong with a member — wraps to the width there is, whatever the user resizes
439
+ the window to. The mark of a member is the one text that does not wrap, because
440
+ it belongs beside its field on one line: a window too narrow for the name, the
441
+ field and the mark squeezes the field, which the user can scroll within, rather
442
+ than cutting off a mark, which they could not read at all.
443
+
444
+ Each kind of text has a colour, so that the explanations do not read as loudly
445
+ as the values and a refused validation does not read like an accepted one.
446
+ Which kind each piece of text is comes from `edit_cfg_json.Emphasis` and is
447
+ therefore the same here as in the Textual backend; what the colours are is this
448
+ package's own, in `EMPHASIS_COLOURS`, because Tk has no theme to ask. They are
449
+ chosen for the light window that Tk gives this editor. A Tk that a platform has
450
+ put into a dark mode would want other values, and that is a theming decision
451
+ the library has not been asked for yet.
452
+
453
+ ## About the keys
454
+
455
+ The keys are the ones the application chose in the `actions` of its
456
+ `edit_cfg_json.Settings`, and with an application that chose nothing they
457
+ are the defaults of `edit_cfg_json.ActionSettings`:
458
+
459
+ | Key | What it does |
460
+ | --- | --- |
461
+ | `ctrl+r`, or `f5` | Validate |
462
+ | `ctrl+s` | Save |
463
+ | `ctrl+shift+s` or `f12` | Save as |
464
+ | `f1`, or `ctrl+g` | Explain |
465
+ | `f2`, or `ctrl+t` | Fold all, or unfold all |
466
+ | `ctrl+q` | Close |
467
+
468
+ Combinations are written in the notation that `ActionSettings` documents,
469
+ which this package translates into the event sequences of Tk: `ctrl+shift+s`
470
+ becomes `<Control-Shift-S>`, and `f5` becomes `<F5>`. A combination this
471
+ translation does not know, or one that Tk itself refuses, leaves that action
472
+ without that key rather than without an editor — every action here has a
473
+ button as well, which is also what an action the application gave no key at
474
+ all keeps. The fold action is offered at all only to a configuration that has
475
+ something to fold, so its keys are free wherever there would be nothing to
476
+ fold.
477
+
478
+ The `cancel` action is bound to nothing in this backend. The questions it would
479
+ leave are put in the toolkit's own dialogs, which answer that key themselves.
480
+
481
+ The bindings are made on the window, so a key that a field does not use for
482
+ itself reaches them wherever the focus is. They are read once, when the
483
+ widgets are built, which is the one thing a later answer from a settings
484
+ callable cannot change.
485
+
486
+ ## Installing edit-cfg-json-tk
487
+
488
+ ### On macOS and Linux
489
+
490
+ To install edit-cfg-json-tk on macOS and Linux, run the following command:
491
+
492
+ ````sh
493
+ pip3 install --upgrade edit-cfg-json-tk
494
+ ````
495
+
496
+ ### On Microsoft Windows
497
+
498
+ To install edit-cfg-json-tk on Microsoft Windows, run the following command:
499
+
500
+ ````sh
501
+ pip install --upgrade edit-cfg-json-tk
502
+ ````
503
+
504
+ ## Documentation
505
+
506
+ - Design and decisions:
507
+ [doc/design.md](https://github.com/tom-bjorkholm/edit-cfg-json/blob/master/doc/design.md)
508
+
509
+ - Public API:
510
+ [edit-cfg-json](https://github.com/tom-bjorkholm/edit-cfg-json/blob/master/doc/edit-cfg-json_api.md),
511
+ [edit-cfg-json-tk](https://github.com/tom-bjorkholm/edit-cfg-json/blob/master/doc/edit-cfg-json-tk_api.md),
512
+ [edit-cfg-json-textual](https://github.com/tom-bjorkholm/edit-cfg-json/blob/master/doc/edit-cfg-json-textual_api.md)
513
+
514
+ - Protected API:
515
+ [edit-cfg-json](https://github.com/tom-bjorkholm/edit-cfg-json/blob/master/doc/edit-cfg-json_protected_api.md),
516
+ [edit-cfg-json-tk](https://github.com/tom-bjorkholm/edit-cfg-json/blob/master/doc/edit-cfg-json-tk_protected_api.md),
517
+ [edit-cfg-json-textual](https://github.com/tom-bjorkholm/edit-cfg-json/blob/master/doc/edit-cfg-json-textual_protected_api.md)
518
+
519
+ - Worked examples:
520
+ [examples/src/example](https://github.com/tom-bjorkholm/edit-cfg-json/blob/master/examples/src/example)
521
+
522
+ ## License
523
+
524
+ edit-cfg-json-tk is released under the MIT License. See the `LICENSE.txt`
525
+ file included in the distribution.
526
+
527
+ ## Test summary
528
+
529
+ - Test result: 1465 passed, 3 deselected in 38s
530
+ - No flake8 warnings.
531
+ - No mypy errors found.
532
+ - No pylint warnings.
533
+ - No python layout warnings.
534
+ - Built version(s): 0.0.2
535
+ - Build and test using Python 3.14.6