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.
- edit_cfg_json_tk-0.0.2/LICENSE.txt +21 -0
- edit_cfg_json_tk-0.0.2/PKG-INFO +535 -0
- edit_cfg_json_tk-0.0.2/README_pypi.md +503 -0
- edit_cfg_json_tk-0.0.2/edit_cfg_json_tk.egg-info/PKG-INFO +535 -0
- edit_cfg_json_tk-0.0.2/edit_cfg_json_tk.egg-info/SOURCES.txt +19 -0
- edit_cfg_json_tk-0.0.2/edit_cfg_json_tk.egg-info/dependency_links.txt +1 -0
- edit_cfg_json_tk-0.0.2/edit_cfg_json_tk.egg-info/entry_points.txt +2 -0
- edit_cfg_json_tk-0.0.2/edit_cfg_json_tk.egg-info/requires.txt +4 -0
- edit_cfg_json_tk-0.0.2/edit_cfg_json_tk.egg-info/top_level.txt +1 -0
- edit_cfg_json_tk-0.0.2/pyproject.toml +36 -0
- edit_cfg_json_tk-0.0.2/setup.cfg +4 -0
- edit_cfg_json_tk-0.0.2/setup.py +22 -0
- edit_cfg_json_tk-0.0.2/src/edit_cfg_json_tk/__init__.py +13 -0
- edit_cfg_json_tk-0.0.2/src/edit_cfg_json_tk/__main__.py +40 -0
- edit_cfg_json_tk-0.0.2/src/edit_cfg_json_tk/key_names.py +125 -0
- edit_cfg_json_tk-0.0.2/src/edit_cfg_json_tk/py.typed +0 -0
- edit_cfg_json_tk-0.0.2/src/edit_cfg_json_tk/scrolling.py +207 -0
- edit_cfg_json_tk-0.0.2/src/edit_cfg_json_tk/tk_ask.py +182 -0
- edit_cfg_json_tk-0.0.2/src/edit_cfg_json_tk/tk_editor.py +940 -0
- edit_cfg_json_tk-0.0.2/src/edit_cfg_json_tk/tk_elements.py +113 -0
- edit_cfg_json_tk-0.0.2/src/edit_cfg_json_tk/tk_look.py +225 -0
|
@@ -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
|