backlogops-gui 0.3__tar.gz → 0.4__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.
- {backlogops_gui-0.3 → backlogops_gui-0.4}/PKG-INFO +21 -5
- {backlogops_gui-0.3 → backlogops_gui-0.4}/README_pypi.md +18 -2
- {backlogops_gui-0.3 → backlogops_gui-0.4}/backlogops_gui.egg-info/PKG-INFO +21 -5
- {backlogops_gui-0.3 → backlogops_gui-0.4}/backlogops_gui.egg-info/SOURCES.txt +4 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/backlogops_gui.egg-info/requires.txt +1 -1
- {backlogops_gui-0.3 → backlogops_gui-0.4}/pyproject.toml +2 -3
- {backlogops_gui-0.3 → backlogops_gui-0.4}/setup.py +2 -2
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/application.py +226 -29
- backlogops_gui-0.4/src/backlogops_gui/auto_scroll.py +37 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/backlog_actions.py +8 -4
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/backlog_window.py +206 -35
- backlogops_gui-0.4/src/backlogops_gui/choice_dialogs.py +132 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/file_choosers.py +7 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/gui_wizard.py +33 -2
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/jira_actions.py +1 -1
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/jira_base.py +4 -4
- backlogops_gui-0.4/src/backlogops_gui/jira_read.py +108 -0
- backlogops_gui-0.4/src/backlogops_gui/token_dialog.py +143 -0
- backlogops_gui-0.4/src/backlogops_gui/wizard_form.py +486 -0
- backlogops_gui-0.4/src/backlogops_gui/wizard_path.py +166 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/wizard_table.py +56 -26
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/wizard_window.py +84 -7
- backlogops_gui-0.3/src/backlogops_gui/choice_dialogs.py +0 -139
- backlogops_gui-0.3/src/backlogops_gui/jira_read.py +0 -79
- {backlogops_gui-0.3 → backlogops_gui-0.4}/LICENSE.txt +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/backlogops_gui.egg-info/dependency_links.txt +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/backlogops_gui.egg-info/entry_points.txt +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/backlogops_gui.egg-info/top_level.txt +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/setup.cfg +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/__init__.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/__main__.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/_migrate_warn.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/backlog_dialogs.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/backlog_io.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/blog_version_reporter.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/close_binding.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/format_dialogs.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/gui_style.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/jira_dialogs.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/jira_order.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/jira_rank.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/jira_rename.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/jira_update.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/jira_write.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/log_buffer.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/modal_dialog.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/py.typed +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/python_version.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/report_windows.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/table_view.py +0 -0
- {backlogops_gui-0.3 → backlogops_gui-0.4}/src/backlogops_gui/tcltk_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: backlogops-gui
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4
|
|
4
4
|
Summary: Graphical user interface for backlog operations.
|
|
5
5
|
Author: Tom Björkholm
|
|
6
6
|
Author-email: Tom Björkholm <klausuler_linnet0q@icloud.com>
|
|
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
|
-
Classifier: Development Status ::
|
|
17
|
+
Classifier: Development Status :: 4 - Beta
|
|
18
18
|
Classifier: Intended Audience :: End Users/Desktop
|
|
19
19
|
Classifier: Intended Audience :: Information Technology
|
|
20
20
|
Classifier: Topic :: Office/Business :: Scheduling
|
|
@@ -22,7 +22,7 @@ Classifier: Typing :: Typed
|
|
|
22
22
|
Requires-Python: >=3.12
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE.txt
|
|
25
|
-
Requires-Dist: backlogops>=0.
|
|
25
|
+
Requires-Dist: backlogops>=0.4
|
|
26
26
|
Requires-Dist: argcomplete>=3.7.0
|
|
27
27
|
Requires-Dist: versionreporter>=0.4
|
|
28
28
|
Dynamic: author
|
|
@@ -46,6 +46,18 @@ There are 3 related packages for backlog operations:
|
|
|
46
46
|
interface to use the functions in the library. It is based on TkInter. The
|
|
47
47
|
ambition is to keep it as a thin wrapper around the library.
|
|
48
48
|
|
|
49
|
+
## Project status
|
|
50
|
+
|
|
51
|
+
backlogops is in beta. The algorithms intended for its initial scope are in
|
|
52
|
+
place, so the main work from here is on the user experience rather than on new
|
|
53
|
+
core functionality. The three packages share a version number and are released
|
|
54
|
+
together.
|
|
55
|
+
|
|
56
|
+
The library is not meant to ever be "finished": using it for real planning keeps
|
|
57
|
+
surfacing new ideas. Some of those will be added to backlogops itself, and
|
|
58
|
+
others will fit better in separate packages built on top of backlogops. Early
|
|
59
|
+
adopters are very welcome, and their feedback shapes what comes next.
|
|
60
|
+
|
|
49
61
|
## Available functionality
|
|
50
62
|
|
|
51
63
|
The following functionality is available in all 3 packages:
|
|
@@ -285,6 +297,10 @@ is in the menus.
|
|
|
285
297
|
- Write configuration...: This lets you write the configuration you
|
|
286
298
|
have in application to a file.
|
|
287
299
|
|
|
300
|
+
- Encrypt Jira API token file...: Encrypt a Jira API token to a
|
|
301
|
+
pass-phrase-protected file, for use with the ENCRYPTED_FILE token
|
|
302
|
+
storage mode.
|
|
303
|
+
|
|
288
304
|
- Help
|
|
289
305
|
|
|
290
306
|
- Report version information: Show version information for the
|
|
@@ -337,9 +353,9 @@ one with the list of releases. You will want to use the menus.
|
|
|
337
353
|
|
|
338
354
|
## Test summary
|
|
339
355
|
|
|
340
|
-
- Test result:
|
|
356
|
+
- Test result: 2638 passed, 7 deselected in 51s
|
|
341
357
|
- No flake8 warnings.
|
|
342
358
|
- No mypy errors found.
|
|
343
359
|
- No python layout warnings.
|
|
344
|
-
- Built version(s): 0.
|
|
360
|
+
- Built version(s): 0.4
|
|
345
361
|
- Build and test using Python 3.14.6
|
|
@@ -14,6 +14,18 @@ There are 3 related packages for backlog operations:
|
|
|
14
14
|
interface to use the functions in the library. It is based on TkInter. The
|
|
15
15
|
ambition is to keep it as a thin wrapper around the library.
|
|
16
16
|
|
|
17
|
+
## Project status
|
|
18
|
+
|
|
19
|
+
backlogops is in beta. The algorithms intended for its initial scope are in
|
|
20
|
+
place, so the main work from here is on the user experience rather than on new
|
|
21
|
+
core functionality. The three packages share a version number and are released
|
|
22
|
+
together.
|
|
23
|
+
|
|
24
|
+
The library is not meant to ever be "finished": using it for real planning keeps
|
|
25
|
+
surfacing new ideas. Some of those will be added to backlogops itself, and
|
|
26
|
+
others will fit better in separate packages built on top of backlogops. Early
|
|
27
|
+
adopters are very welcome, and their feedback shapes what comes next.
|
|
28
|
+
|
|
17
29
|
## Available functionality
|
|
18
30
|
|
|
19
31
|
The following functionality is available in all 3 packages:
|
|
@@ -253,6 +265,10 @@ is in the menus.
|
|
|
253
265
|
- Write configuration...: This lets you write the configuration you
|
|
254
266
|
have in application to a file.
|
|
255
267
|
|
|
268
|
+
- Encrypt Jira API token file...: Encrypt a Jira API token to a
|
|
269
|
+
pass-phrase-protected file, for use with the ENCRYPTED_FILE token
|
|
270
|
+
storage mode.
|
|
271
|
+
|
|
256
272
|
- Help
|
|
257
273
|
|
|
258
274
|
- Report version information: Show version information for the
|
|
@@ -305,9 +321,9 @@ one with the list of releases. You will want to use the menus.
|
|
|
305
321
|
|
|
306
322
|
## Test summary
|
|
307
323
|
|
|
308
|
-
- Test result:
|
|
324
|
+
- Test result: 2638 passed, 7 deselected in 51s
|
|
309
325
|
- No flake8 warnings.
|
|
310
326
|
- No mypy errors found.
|
|
311
327
|
- No python layout warnings.
|
|
312
|
-
- Built version(s): 0.
|
|
328
|
+
- Built version(s): 0.4
|
|
313
329
|
- Build and test using Python 3.14.6
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: backlogops-gui
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4
|
|
4
4
|
Summary: Graphical user interface for backlog operations.
|
|
5
5
|
Author: Tom Björkholm
|
|
6
6
|
Author-email: Tom Björkholm <klausuler_linnet0q@icloud.com>
|
|
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
|
-
Classifier: Development Status ::
|
|
17
|
+
Classifier: Development Status :: 4 - Beta
|
|
18
18
|
Classifier: Intended Audience :: End Users/Desktop
|
|
19
19
|
Classifier: Intended Audience :: Information Technology
|
|
20
20
|
Classifier: Topic :: Office/Business :: Scheduling
|
|
@@ -22,7 +22,7 @@ Classifier: Typing :: Typed
|
|
|
22
22
|
Requires-Python: >=3.12
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE.txt
|
|
25
|
-
Requires-Dist: backlogops>=0.
|
|
25
|
+
Requires-Dist: backlogops>=0.4
|
|
26
26
|
Requires-Dist: argcomplete>=3.7.0
|
|
27
27
|
Requires-Dist: versionreporter>=0.4
|
|
28
28
|
Dynamic: author
|
|
@@ -46,6 +46,18 @@ There are 3 related packages for backlog operations:
|
|
|
46
46
|
interface to use the functions in the library. It is based on TkInter. The
|
|
47
47
|
ambition is to keep it as a thin wrapper around the library.
|
|
48
48
|
|
|
49
|
+
## Project status
|
|
50
|
+
|
|
51
|
+
backlogops is in beta. The algorithms intended for its initial scope are in
|
|
52
|
+
place, so the main work from here is on the user experience rather than on new
|
|
53
|
+
core functionality. The three packages share a version number and are released
|
|
54
|
+
together.
|
|
55
|
+
|
|
56
|
+
The library is not meant to ever be "finished": using it for real planning keeps
|
|
57
|
+
surfacing new ideas. Some of those will be added to backlogops itself, and
|
|
58
|
+
others will fit better in separate packages built on top of backlogops. Early
|
|
59
|
+
adopters are very welcome, and their feedback shapes what comes next.
|
|
60
|
+
|
|
49
61
|
## Available functionality
|
|
50
62
|
|
|
51
63
|
The following functionality is available in all 3 packages:
|
|
@@ -285,6 +297,10 @@ is in the menus.
|
|
|
285
297
|
- Write configuration...: This lets you write the configuration you
|
|
286
298
|
have in application to a file.
|
|
287
299
|
|
|
300
|
+
- Encrypt Jira API token file...: Encrypt a Jira API token to a
|
|
301
|
+
pass-phrase-protected file, for use with the ENCRYPTED_FILE token
|
|
302
|
+
storage mode.
|
|
303
|
+
|
|
288
304
|
- Help
|
|
289
305
|
|
|
290
306
|
- Report version information: Show version information for the
|
|
@@ -337,9 +353,9 @@ one with the list of releases. You will want to use the menus.
|
|
|
337
353
|
|
|
338
354
|
## Test summary
|
|
339
355
|
|
|
340
|
-
- Test result:
|
|
356
|
+
- Test result: 2638 passed, 7 deselected in 51s
|
|
341
357
|
- No flake8 warnings.
|
|
342
358
|
- No mypy errors found.
|
|
343
359
|
- No python layout warnings.
|
|
344
|
-
- Built version(s): 0.
|
|
360
|
+
- Built version(s): 0.4
|
|
345
361
|
- Build and test using Python 3.14.6
|
|
@@ -12,6 +12,7 @@ src/backlogops_gui/__init__.py
|
|
|
12
12
|
src/backlogops_gui/__main__.py
|
|
13
13
|
src/backlogops_gui/_migrate_warn.py
|
|
14
14
|
src/backlogops_gui/application.py
|
|
15
|
+
src/backlogops_gui/auto_scroll.py
|
|
15
16
|
src/backlogops_gui/backlog_actions.py
|
|
16
17
|
src/backlogops_gui/backlog_dialogs.py
|
|
17
18
|
src/backlogops_gui/backlog_io.py
|
|
@@ -39,5 +40,8 @@ src/backlogops_gui/python_version.py
|
|
|
39
40
|
src/backlogops_gui/report_windows.py
|
|
40
41
|
src/backlogops_gui/table_view.py
|
|
41
42
|
src/backlogops_gui/tcltk_version.py
|
|
43
|
+
src/backlogops_gui/token_dialog.py
|
|
44
|
+
src/backlogops_gui/wizard_form.py
|
|
45
|
+
src/backlogops_gui/wizard_path.py
|
|
42
46
|
src/backlogops_gui/wizard_table.py
|
|
43
47
|
src/backlogops_gui/wizard_window.py
|
|
@@ -4,7 +4,6 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "backlogops-gui"
|
|
7
|
-
version = "0.3"
|
|
8
7
|
authors = [
|
|
9
8
|
{ name="Tom Björkholm", email="klausuler_linnet0q@icloud.com" },
|
|
10
9
|
]
|
|
@@ -22,13 +21,13 @@ classifiers = [
|
|
|
22
21
|
"Programming Language :: Python :: 3.13",
|
|
23
22
|
"Programming Language :: Python :: 3.14",
|
|
24
23
|
"Operating System :: OS Independent",
|
|
25
|
-
"Development Status ::
|
|
24
|
+
"Development Status :: 4 - Beta",
|
|
26
25
|
"Intended Audience :: End Users/Desktop",
|
|
27
26
|
"Intended Audience :: Information Technology",
|
|
28
27
|
"Topic :: Office/Business :: Scheduling",
|
|
29
28
|
"Typing :: Typed"
|
|
30
29
|
]
|
|
31
|
-
dynamic = ["dependencies"]
|
|
30
|
+
dynamic = ["dependencies", "version"]
|
|
32
31
|
|
|
33
32
|
[project.urls]
|
|
34
33
|
"Homepage" = "https://github.com/tom-bjorkholm/backlog-ops"
|
|
@@ -5,7 +5,7 @@ from setuptools import setup
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name='backlogops-gui',
|
|
8
|
-
version='0.
|
|
8
|
+
version='0.4',
|
|
9
9
|
description='Graphical user interface for backlog operations.',
|
|
10
10
|
author='Tom Björkholm',
|
|
11
11
|
author_email='klausuler_linnet0q@icloud.com',
|
|
@@ -19,7 +19,7 @@ setup(
|
|
|
19
19
|
]
|
|
20
20
|
},
|
|
21
21
|
install_requires=[
|
|
22
|
-
'backlogops >= 0.
|
|
22
|
+
'backlogops >= 0.4',
|
|
23
23
|
'argcomplete >= 3.7.0',
|
|
24
24
|
'versionreporter >= 0.4'
|
|
25
25
|
]
|
|
@@ -6,8 +6,13 @@ or from Jira, loads or replaces the active configuration from a file, runs
|
|
|
6
6
|
the teams configuration wizard, creates a stand-alone input or output
|
|
7
7
|
preset file, migrates a stand-alone preset file to the current format,
|
|
8
8
|
writes the running configuration to a file, and creates a demonstration
|
|
9
|
-
backlog.
|
|
10
|
-
|
|
9
|
+
backlog. The configuration wizard and the preset wizard first ask whether
|
|
10
|
+
to start empty or be pre-filled from an existing file, so the user can
|
|
11
|
+
edit an existing configuration instead of entering everything again. Each
|
|
12
|
+
backlog opens in its own window, whose information region records where the
|
|
13
|
+
data came from and when, marks the window when the backlog has been
|
|
14
|
+
modified, and offers a "Read again" button that re-reads the same source.
|
|
15
|
+
On macOS the menu bar sits at the top of the display rather than in
|
|
11
16
|
the window, so the main window body shows a short description, the current
|
|
12
17
|
configuration status, and a log of the most recent diagnostic messages, to
|
|
13
18
|
make clear that the application is running. The teams configuration is
|
|
@@ -28,29 +33,35 @@ import argparse
|
|
|
28
33
|
import threading
|
|
29
34
|
import tkinter as tk
|
|
30
35
|
from io import StringIO
|
|
36
|
+
from pathlib import Path
|
|
31
37
|
from tkinter import messagebox, ttk
|
|
32
38
|
from typing import Callable, Optional, TextIO, TypeVar
|
|
33
39
|
import argcomplete
|
|
34
40
|
from config_as_json import Config, migrate_cfg
|
|
35
41
|
from config_as_json.file_extension import fix_file_extension
|
|
36
|
-
from tableio_cfg_json import WizardUiBridge
|
|
37
42
|
from backlogops import (
|
|
38
43
|
AvailableTeams, BacklogOpsConfig, BacklogReleases, GuiDisplayConfig,
|
|
39
44
|
InputFormatConfig, Levels, OutputFormatConfig, Status, get_demo_backlog,
|
|
40
|
-
get_backlog_ops_config, backlog_ops_wizard, preset_wizard
|
|
45
|
+
get_backlog_ops_config, backlog_ops_wizard, preset_wizard,
|
|
46
|
+
read_backlog_ops_config, read_io_preset, safe_write_config,
|
|
47
|
+
encrypt_token_file, encrypt_token_to_file)
|
|
41
48
|
from backlogops_gui.backlog_io import read_backlog
|
|
42
|
-
from backlogops_gui.backlog_window import
|
|
49
|
+
from backlogops_gui.backlog_window import (
|
|
50
|
+
BacklogWindow, BacklogSource, JiraHandlers, current_time)
|
|
43
51
|
from backlogops_gui.blog_version_reporter import BloGuiVersionReporter
|
|
44
52
|
from backlogops_gui.gui_wizard import TkWizardBridge
|
|
45
53
|
from backlogops_gui.choice_dialogs import (
|
|
46
|
-
ConfigChoice, PresetKind, ask_no_config_choice,
|
|
54
|
+
ConfigChoice, PresetKind, SourceChoice, ask_no_config_choice,
|
|
55
|
+
ask_preset_kind, ask_source_choice)
|
|
47
56
|
from backlogops_gui.file_choosers import (
|
|
48
|
-
choose_config_file, choose_existing_config,
|
|
49
|
-
choose_migrated_preset, choose_preset_to_migrate)
|
|
57
|
+
choose_config_file, choose_existing_config, choose_existing_preset,
|
|
58
|
+
choose_input_file, choose_migrated_preset, choose_preset_to_migrate)
|
|
50
59
|
from backlogops_gui.format_dialogs import ask_read_options
|
|
60
|
+
from backlogops_gui.token_dialog import ask_encrypt_token, EncryptTokenRequest
|
|
51
61
|
from backlogops_gui.jira_actions import JiraActions
|
|
52
62
|
from backlogops_gui.log_buffer import LogBuffer
|
|
53
|
-
from backlogops_gui._migrate_warn import GuiMigrateWarnHook
|
|
63
|
+
from backlogops_gui._migrate_warn import GuiMigrateWarnHook, \
|
|
64
|
+
GuiPresetMigrateWarnHook
|
|
54
65
|
from backlogops_gui.python_version import check_python_version
|
|
55
66
|
from backlogops_gui.tcltk_version import check_tcltk_version
|
|
56
67
|
|
|
@@ -75,6 +86,28 @@ PRESET_CLASSES: dict[PresetKind, type[Config]] = {
|
|
|
75
86
|
PresetKind.INPUT: InputFormatConfig,
|
|
76
87
|
PresetKind.OUTPUT: OutputFormatConfig}
|
|
77
88
|
_WizardConfig = TypeVar('_WizardConfig', bound=Config)
|
|
89
|
+
_CONFIG_SOURCE_TITLE = 'Configuration wizard'
|
|
90
|
+
_CONFIG_SOURCE_TEXT = (
|
|
91
|
+
'Start the configuration wizard from scratch, or base it on an '
|
|
92
|
+
'existing configuration file so you edit its values instead of '
|
|
93
|
+
'entering everything again?')
|
|
94
|
+
_PRESET_SOURCE_TITLE = 'IO preset'
|
|
95
|
+
_PRESET_SOURCE_TEXT = (
|
|
96
|
+
'Start the preset wizard from scratch, or base it on an existing '
|
|
97
|
+
'preset file so you edit its values instead of entering everything '
|
|
98
|
+
'again?')
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _read_config_prefill(path: str, captured: TextIO) -> BacklogOpsConfig:
|
|
102
|
+
"""Read an existing configuration to pre-fill the wizard."""
|
|
103
|
+
return read_backlog_ops_config(path, captured,
|
|
104
|
+
auto_ch_hook=GuiMigrateWarnHook())
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _read_preset_prefill(path: str, captured: TextIO
|
|
108
|
+
) -> InputFormatConfig | OutputFormatConfig:
|
|
109
|
+
"""Read an existing preset, auto-detecting direction, to pre-fill."""
|
|
110
|
+
return read_io_preset(path, GuiPresetMigrateWarnHook(), captured)
|
|
78
111
|
|
|
79
112
|
|
|
80
113
|
def initial_config(config_arg: Optional[str], sink: Optional[TextIO] = None
|
|
@@ -229,18 +262,20 @@ class BacklogApp:
|
|
|
229
262
|
self.config_source = path
|
|
230
263
|
return True
|
|
231
264
|
|
|
232
|
-
def _run_bridge_wizard(self,
|
|
233
|
-
|
|
234
|
-
|
|
265
|
+
def _run_bridge_wizard(self, wizard: Callable[..., _WizardConfig],
|
|
266
|
+
error_title: str,
|
|
267
|
+
default: Optional[_WizardConfig] = None
|
|
268
|
+
) -> Optional[_WizardConfig]:
|
|
235
269
|
"""Run a wizard over a fresh Tk bridge, returning its config or None.
|
|
236
270
|
|
|
237
|
-
|
|
271
|
+
The wizard is pre-filled from ``default`` when one is given. An
|
|
272
|
+
abandoned wizard ends in ``EOFError`` and yields None; any other
|
|
238
273
|
wizard failure is reported under ``error_title`` and also yields
|
|
239
274
|
None. The bridge window is always closed afterwards.
|
|
240
275
|
"""
|
|
241
276
|
bridge = TkWizardBridge(self.root, self.log)
|
|
242
277
|
try:
|
|
243
|
-
return wizard(bridge)
|
|
278
|
+
return wizard(bridge, default=default)
|
|
244
279
|
except EOFError:
|
|
245
280
|
return None
|
|
246
281
|
except IO_ERRORS as error:
|
|
@@ -249,9 +284,57 @@ class BacklogApp:
|
|
|
249
284
|
finally:
|
|
250
285
|
bridge.close()
|
|
251
286
|
|
|
252
|
-
def run_wizard(self
|
|
287
|
+
def run_wizard(self, default: Optional[BacklogOpsConfig] = None
|
|
288
|
+
) -> Optional[BacklogOpsConfig]:
|
|
253
289
|
"""Run the config wizard and return its configuration, or None."""
|
|
254
|
-
return self._run_bridge_wizard(backlog_ops_wizard, 'Wizard error'
|
|
290
|
+
return self._run_bridge_wizard(backlog_ops_wizard, 'Wizard error',
|
|
291
|
+
default)
|
|
292
|
+
|
|
293
|
+
def _prefill_default(self, title: str, text: str,
|
|
294
|
+
chooser: Callable[[tk.Misc], Optional[str]],
|
|
295
|
+
reader: Callable[[str, TextIO], _WizardConfig]
|
|
296
|
+
) -> tuple[bool, Optional[_WizardConfig]]:
|
|
297
|
+
"""Ask whether to base a wizard on a file and read it if so.
|
|
298
|
+
|
|
299
|
+
Returns a ``(proceed, default)`` pair. ``proceed`` is False when
|
|
300
|
+
the user cancels the source dialog or the file chooser, or the
|
|
301
|
+
chosen file cannot be read, and the caller then does nothing.
|
|
302
|
+
``default`` is the configuration read from the chosen file, or
|
|
303
|
+
None to start the wizard empty.
|
|
304
|
+
"""
|
|
305
|
+
choice = ask_source_choice(self.root, title, text)
|
|
306
|
+
if choice is SourceChoice.CANCEL:
|
|
307
|
+
return False, None
|
|
308
|
+
if choice is SourceChoice.SCRATCH:
|
|
309
|
+
return True, None
|
|
310
|
+
path = chooser(self.root)
|
|
311
|
+
if path is None:
|
|
312
|
+
return False, None
|
|
313
|
+
return self._read_prefill(path, reader)
|
|
314
|
+
|
|
315
|
+
def _read_prefill(self, path: str,
|
|
316
|
+
reader: Callable[[str, TextIO], _WizardConfig]
|
|
317
|
+
) -> tuple[bool, Optional[_WizardConfig]]:
|
|
318
|
+
"""Read a pre-fill file, reporting a failure and yielding no default.
|
|
319
|
+
|
|
320
|
+
The reader can raise the IO errors, or ``SystemExit`` from the
|
|
321
|
+
configuration factory when the file is missing or is not a preset.
|
|
322
|
+
"""
|
|
323
|
+
captured = StringIO()
|
|
324
|
+
try:
|
|
325
|
+
return True, reader(path, captured)
|
|
326
|
+
except SystemExit:
|
|
327
|
+
return self._prefill_failed(captured, f'Could not read {path}.')
|
|
328
|
+
except IO_ERRORS as error:
|
|
329
|
+
return self._prefill_failed(captured, str(error))
|
|
330
|
+
|
|
331
|
+
def _prefill_failed(self, captured: StringIO,
|
|
332
|
+
fallback: str) -> tuple[bool, None]:
|
|
333
|
+
"""Log captured diagnostics and report a pre-fill read failure."""
|
|
334
|
+
self.log.write(captured.getvalue())
|
|
335
|
+
self.show_error('Could not read file',
|
|
336
|
+
_config_failure(captured, fallback))
|
|
337
|
+
return False, None
|
|
255
338
|
|
|
256
339
|
def _load_config_file(self) -> None:
|
|
257
340
|
"""Load a chosen configuration file and make it the active config.
|
|
@@ -276,8 +359,20 @@ class BacklogApp:
|
|
|
276
359
|
f'Loaded configuration from {path}.')
|
|
277
360
|
|
|
278
361
|
def run_config_wizard(self) -> None:
|
|
279
|
-
"""
|
|
280
|
-
|
|
362
|
+
"""Ask the source, run the wizard, and activate a new config.
|
|
363
|
+
|
|
364
|
+
The wizard may start from scratch or be pre-filled from an
|
|
365
|
+
existing configuration file the user chooses. Its result becomes
|
|
366
|
+
the active configuration; writing it to a file stays with the
|
|
367
|
+
``Write configuration…`` action.
|
|
368
|
+
"""
|
|
369
|
+
proceed, default = self._prefill_default(_CONFIG_SOURCE_TITLE,
|
|
370
|
+
_CONFIG_SOURCE_TEXT,
|
|
371
|
+
choose_existing_config,
|
|
372
|
+
_read_config_prefill)
|
|
373
|
+
if not proceed:
|
|
374
|
+
return
|
|
375
|
+
config = self.run_wizard(default)
|
|
281
376
|
if config is not None:
|
|
282
377
|
self.config = config
|
|
283
378
|
self.config_source = 'the wizard'
|
|
@@ -285,8 +380,20 @@ class BacklogApp:
|
|
|
285
380
|
self.show_info('Wizard', 'The new configuration is now active.')
|
|
286
381
|
|
|
287
382
|
def create_preset_file(self) -> None:
|
|
288
|
-
"""
|
|
289
|
-
|
|
383
|
+
"""Ask the source, run the IO preset wizard, and write the preset.
|
|
384
|
+
|
|
385
|
+
The wizard may start from scratch or be pre-filled from an
|
|
386
|
+
existing preset file the user chooses; its direction is detected
|
|
387
|
+
from the file.
|
|
388
|
+
"""
|
|
389
|
+
proceed, default = self._prefill_default(_PRESET_SOURCE_TITLE,
|
|
390
|
+
_PRESET_SOURCE_TEXT,
|
|
391
|
+
choose_existing_preset,
|
|
392
|
+
_read_preset_prefill)
|
|
393
|
+
if not proceed:
|
|
394
|
+
return
|
|
395
|
+
config = self._run_bridge_wizard(preset_wizard, 'Preset wizard error',
|
|
396
|
+
default)
|
|
290
397
|
if config is not None:
|
|
291
398
|
self._write_to_chosen(config, 'Could not write preset',
|
|
292
399
|
'Wrote preset')
|
|
@@ -351,20 +458,74 @@ class BacklogApp:
|
|
|
351
458
|
"""Write a configuration to a user-chosen file and report the outcome.
|
|
352
459
|
|
|
353
460
|
The chosen filename receives the ``.cfg`` extension when missing. A
|
|
354
|
-
cancelled chooser writes nothing
|
|
355
|
-
|
|
461
|
+
cancelled chooser writes nothing, and overwriting an existing file
|
|
462
|
+
is confirmed first. The write is crash-safe: the configuration is
|
|
463
|
+
put in a ``.in_progress`` sibling and only then moved onto the
|
|
464
|
+
output file, so a crash loses neither the old file nor the new one.
|
|
465
|
+
A write failure is reported under ``fail_title`` and a success
|
|
466
|
+
under ``ok_title``.
|
|
356
467
|
"""
|
|
357
468
|
path = choose_config_file(self.root)
|
|
358
469
|
if path is None:
|
|
359
470
|
return
|
|
360
471
|
path = fix_file_extension(path, CONFIG_EXTENSION)
|
|
472
|
+
if not self._confirm_overwrite(path):
|
|
473
|
+
return
|
|
361
474
|
try:
|
|
362
|
-
config
|
|
475
|
+
safe_write_config(config, path, self.log)
|
|
363
476
|
except IO_ERRORS as error:
|
|
364
477
|
self.show_error(fail_title, str(error))
|
|
365
478
|
return
|
|
366
479
|
self.show_info(ok_title, f'Wrote {path}')
|
|
367
480
|
|
|
481
|
+
def _confirm_overwrite(self, path: str) -> bool:
|
|
482
|
+
"""Confirm overwriting an existing file; a new file needs no asking."""
|
|
483
|
+
if not Path(path).exists():
|
|
484
|
+
return True
|
|
485
|
+
return self._ok_to_overwrite(Path(path))
|
|
486
|
+
|
|
487
|
+
def _ok_to_overwrite(self, path: Path) -> bool:
|
|
488
|
+
"""Ask whether to overwrite a file, as the token writer requires."""
|
|
489
|
+
return messagebox.askyesno('Overwrite file?',
|
|
490
|
+
f'{path} already exists. Overwrite it?',
|
|
491
|
+
parent=self.root)
|
|
492
|
+
|
|
493
|
+
def encrypt_token(self) -> None:
|
|
494
|
+
"""Encrypt a Jira API token to a file chosen in a dialog.
|
|
495
|
+
|
|
496
|
+
The dialog gathers a typed token or a clear text token file, the
|
|
497
|
+
encrypted file to write, and the pass phrase entered twice. An
|
|
498
|
+
existing output file is only overwritten after confirmation, and
|
|
499
|
+
any failure is reported.
|
|
500
|
+
"""
|
|
501
|
+
request = ask_encrypt_token(self.root)
|
|
502
|
+
if request is None:
|
|
503
|
+
return
|
|
504
|
+
if self._run_encrypt(request):
|
|
505
|
+
self.show_info('Token encrypted', f'Wrote {request.out_file}')
|
|
506
|
+
|
|
507
|
+
def _run_encrypt(self, request: EncryptTokenRequest) -> bool:
|
|
508
|
+
"""Encrypt the requested token, reporting any failure."""
|
|
509
|
+
try:
|
|
510
|
+
self._do_encrypt(request)
|
|
511
|
+
except IO_ERRORS as error:
|
|
512
|
+
self.show_error('Could not encrypt token', str(error))
|
|
513
|
+
return False
|
|
514
|
+
return True
|
|
515
|
+
|
|
516
|
+
def _do_encrypt(self, request: EncryptTokenRequest) -> None:
|
|
517
|
+
"""Dispatch to the token or the file encryption library function."""
|
|
518
|
+
if request.token is not None:
|
|
519
|
+
encrypt_token_to_file(request.token, passphrase=request.passphrase,
|
|
520
|
+
filename=request.out_file,
|
|
521
|
+
ok_to_overwrite=self._ok_to_overwrite)
|
|
522
|
+
return
|
|
523
|
+
assert request.clear_file is not None
|
|
524
|
+
encrypt_token_file(clear_file=request.clear_file,
|
|
525
|
+
encrypted_file=request.out_file,
|
|
526
|
+
passphrase=request.passphrase,
|
|
527
|
+
ok_to_overwrite=self._ok_to_overwrite)
|
|
528
|
+
|
|
368
529
|
def read_backlog_file(self) -> None:
|
|
369
530
|
"""Read a backlog from a chosen file into a new window."""
|
|
370
531
|
path = choose_input_file(self.root)
|
|
@@ -375,20 +536,52 @@ class BacklogApp:
|
|
|
375
536
|
sorted(presets) if presets else None)
|
|
376
537
|
if options is None:
|
|
377
538
|
return
|
|
539
|
+
value = options.config_value
|
|
378
540
|
try:
|
|
379
|
-
data = read_backlog(path,
|
|
380
|
-
self.
|
|
541
|
+
data = read_backlog(path, value, presets, self.log, self.levels(),
|
|
542
|
+
self.status_map())
|
|
381
543
|
except IO_ERRORS as error:
|
|
382
544
|
self.show_error('Could not read file', str(error))
|
|
383
545
|
return
|
|
384
|
-
self.
|
|
546
|
+
source = self._file_source(path, value, presets)
|
|
547
|
+
self.open_backlog(data, path, source=source,
|
|
548
|
+
reload=self._file_reload(path, value))
|
|
549
|
+
|
|
550
|
+
@staticmethod
|
|
551
|
+
def _file_source(path: str, value: Optional[str],
|
|
552
|
+
presets: Optional[dict[str, InputFormatConfig]]
|
|
553
|
+
) -> BacklogSource:
|
|
554
|
+
"""Describe a file backlog source, naming a preset when used."""
|
|
555
|
+
preset = value if value and presets and value in presets else None
|
|
556
|
+
return BacklogSource(kind='file', read_time=current_time(),
|
|
557
|
+
file_name=path, preset_name=preset)
|
|
558
|
+
|
|
559
|
+
def _file_reload(self, path: str, value: Optional[str]) -> Callable[
|
|
560
|
+
[Callable[[BacklogReleases, Optional[str]], None]], None]:
|
|
561
|
+
"""Return a reload re-reading the same file with the current config."""
|
|
562
|
+
def reload(apply: Callable[[BacklogReleases, Optional[str]], None]
|
|
563
|
+
) -> None:
|
|
564
|
+
"""Re-read the file, reporting a failure and applying success."""
|
|
565
|
+
try:
|
|
566
|
+
data = read_backlog(path, value, self.in_presets(), self.log,
|
|
567
|
+
self.levels(), self.status_map())
|
|
568
|
+
except IO_ERRORS as error:
|
|
569
|
+
self.show_error('Could not read file', str(error))
|
|
570
|
+
return
|
|
571
|
+
apply(data, None)
|
|
572
|
+
return reload
|
|
385
573
|
|
|
386
574
|
def new_demo_backlog(self) -> None:
|
|
387
575
|
"""Open a demonstration backlog in a new window."""
|
|
388
|
-
|
|
576
|
+
source = BacklogSource(kind='demo', read_time=current_time())
|
|
577
|
+
self.open_backlog(get_demo_backlog(), 'Demo backlog', source=source)
|
|
389
578
|
|
|
390
579
|
def open_backlog(self, data: BacklogReleases, title: str,
|
|
391
|
-
warning: Optional[str] = None
|
|
580
|
+
warning: Optional[str] = None, *,
|
|
581
|
+
source: Optional[BacklogSource] = None,
|
|
582
|
+
reload: Optional[Callable[
|
|
583
|
+
[Callable[[BacklogReleases, Optional[str]], None]],
|
|
584
|
+
None]] = None) -> None:
|
|
392
585
|
"""Open one backlog and its releases in a new window."""
|
|
393
586
|
handlers = JiraHandlers(
|
|
394
587
|
add_backlog=self.jira.writer.backlog_action(),
|
|
@@ -400,7 +593,8 @@ class BacklogApp:
|
|
|
400
593
|
rename_releases=self.jira.renamer.rename_action())
|
|
401
594
|
BacklogWindow(self.root, data, title, self.out_presets,
|
|
402
595
|
self.available_teams, self.log, self.levels,
|
|
403
|
-
self.gui_display, warning, handlers
|
|
596
|
+
self.gui_display, warning, handlers, source=source,
|
|
597
|
+
reload=reload)
|
|
404
598
|
|
|
405
599
|
def report_versions(self) -> None:
|
|
406
600
|
"""Report version information into the log on a worker thread.
|
|
@@ -461,6 +655,9 @@ class BacklogApp:
|
|
|
461
655
|
command=self.migrate_preset_file)
|
|
462
656
|
menu.add_command(label='Write configuration…',
|
|
463
657
|
command=self.write_config)
|
|
658
|
+
menu.add_separator()
|
|
659
|
+
menu.add_command(label='Encrypt Jira API token file…',
|
|
660
|
+
command=self.encrypt_token)
|
|
464
661
|
menubar.add_cascade(label='Configuration', menu=menu)
|
|
465
662
|
|
|
466
663
|
def _add_help_menu(self, menubar: tk.Menu) -> None:
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#! /usr/local/bin/python3
|
|
2
|
+
"""A scroll command that shows a scrollbar only while it can scroll.
|
|
3
|
+
|
|
4
|
+
A table that fits its area needs no scrollbar, and a scrollbar that is
|
|
5
|
+
always shown wastes space and hints at hidden content that is not there.
|
|
6
|
+
:func:`auto_hide` returns the scroll command for a scrolling widget: the
|
|
7
|
+
command hides the scrollbar while the whole range is visible and shows it
|
|
8
|
+
again once the widget grows past its area. It works for any widget that
|
|
9
|
+
reports its position through an ``xscrollcommand`` or ``yscrollcommand``,
|
|
10
|
+
so the wizard table canvas and the read-only backlog tree can share it.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# Copyright (c) 2026, Tom Björkholm
|
|
14
|
+
# MIT License
|
|
15
|
+
|
|
16
|
+
from tkinter import ttk
|
|
17
|
+
from typing import Callable
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def auto_hide(scrollbar: ttk.Scrollbar
|
|
21
|
+
) -> Callable[[float | str, float | str], None]:
|
|
22
|
+
"""Return a scroll command that hides the scrollbar when it is full.
|
|
23
|
+
|
|
24
|
+
The result is used as a widget's ``xscrollcommand`` or
|
|
25
|
+
``yscrollcommand``. Tk reports the position as two fractions, which it
|
|
26
|
+
passes as strings, so the command accepts either a number or its
|
|
27
|
+
string form. The scrollbar must be laid out with the grid manager,
|
|
28
|
+
whose ``grid_remove`` remembers its cell across the hide.
|
|
29
|
+
"""
|
|
30
|
+
def command(first: float | str, last: float | str) -> None:
|
|
31
|
+
"""Hide or show the scrollbar, then move its thumb to the view."""
|
|
32
|
+
if float(first) <= 0.0 and float(last) >= 1.0:
|
|
33
|
+
scrollbar.grid_remove()
|
|
34
|
+
else:
|
|
35
|
+
scrollbar.grid()
|
|
36
|
+
scrollbar.set(first, last)
|
|
37
|
+
return command
|