backlogops-cli 0.3__tar.gz → 0.5__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_cli-0.3 → backlogops_cli-0.5}/PKG-INFO +17 -5
- {backlogops_cli-0.3 → backlogops_cli-0.5}/README_pypi.md +14 -2
- {backlogops_cli-0.3 → backlogops_cli-0.5}/backlogops_cli.egg-info/PKG-INFO +17 -5
- {backlogops_cli-0.3 → backlogops_cli-0.5}/backlogops_cli.egg-info/SOURCES.txt +1 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/backlogops_cli.egg-info/requires.txt +1 -1
- {backlogops_cli-0.3 → backlogops_cli-0.5}/pyproject.toml +2 -3
- {backlogops_cli-0.3 → backlogops_cli-0.5}/setup.py +2 -2
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/_command_io.py +96 -1
- backlogops_cli-0.5/src/backlogops_cli/_wizard_io.py +126 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/add_releases_to_jira.py +23 -54
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/add_to_jira.py +21 -54
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/config_wizard.py +16 -5
- backlogops_cli-0.5/src/backlogops_cli/encrypt_token_file.py +121 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/jira_fields.py +4 -12
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/order_by_deps.py +2 -3
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/order_by_keys.py +2 -3
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/order_by_release.py +2 -3
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/order_releases.py +2 -3
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/order_releases_in_jira.py +6 -16
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/preset_wizard.py +20 -5
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/rank_in_jira.py +6 -16
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/read_jira.py +2 -8
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/rename_releases_in_jira.py +6 -16
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/update_backlog_in_jira.py +6 -18
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/update_releases_in_jira.py +6 -17
- backlogops_cli-0.3/src/backlogops_cli/_wizard_io.py +0 -92
- {backlogops_cli-0.3 → backlogops_cli-0.5}/LICENSE.txt +0 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/backlogops_cli.egg-info/dependency_links.txt +0 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/backlogops_cli.egg-info/top_level.txt +0 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/setup.cfg +0 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/__init__.py +0 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/__main__.py +0 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/_migrate_warn.py +0 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/adjust_release_content.py +0 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/bloc_version_reporter.py +0 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/convert.py +0 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/demo_backlog.py +0 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/estimate_ready_date.py +0 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/extract_keys.py +0 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/list.py +0 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/migrate_cfg.py +0 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/plan_release_dates.py +0 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/py.typed +0 -0
- {backlogops_cli-0.3 → backlogops_cli-0.5}/src/backlogops_cli/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: backlogops-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5
|
|
4
4
|
Summary: Command line 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: Environment :: Console
|
|
19
19
|
Classifier: Intended Audience :: Developers
|
|
20
20
|
Classifier: Intended Audience :: End Users/Desktop
|
|
@@ -25,7 +25,7 @@ Requires-Python: >=3.12
|
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
License-File: LICENSE.txt
|
|
27
27
|
Requires-Dist: argcomplete>=3.7.0
|
|
28
|
-
Requires-Dist: backlogops>=0.
|
|
28
|
+
Requires-Dist: backlogops>=0.5
|
|
29
29
|
Requires-Dist: versionreporter>=0.4
|
|
30
30
|
Dynamic: author
|
|
31
31
|
Dynamic: license-file
|
|
@@ -48,6 +48,14 @@ There are 3 related packages for backlog operations:
|
|
|
48
48
|
interface to use the functions in the library. It is based on TkInter. The
|
|
49
49
|
ambition is to keep it as a thin wrapper around the library.
|
|
50
50
|
|
|
51
|
+
## Project status
|
|
52
|
+
|
|
53
|
+
The three packages share a version number and are released together.
|
|
54
|
+
|
|
55
|
+
The library is not meant to ever be "finished": using it for real planning keeps
|
|
56
|
+
surfacing new ideas. Some of those will be added to backlogops itself, and
|
|
57
|
+
others will fit better in separate packages built on top of backlogops.
|
|
58
|
+
|
|
51
59
|
## Available functionality
|
|
52
60
|
|
|
53
61
|
The following functionality is available in all 3 packages:
|
|
@@ -249,6 +257,9 @@ backlogops_cli serves 2 purposes:
|
|
|
249
257
|
python3 -m backlogops_cli.demo_backlog
|
|
250
258
|
Write a demonstration backlog and releases to a file
|
|
251
259
|
|
|
260
|
+
python3 -m backlogops_cli.encrypt_token_file
|
|
261
|
+
Encrypt a Jira API token file with a pass phrase.
|
|
262
|
+
|
|
252
263
|
python3 -m backlogops_cli.estimate_ready_date
|
|
253
264
|
Estimate ready dates for the backlog items
|
|
254
265
|
|
|
@@ -307,9 +318,10 @@ backlogops_cli serves 2 purposes:
|
|
|
307
318
|
|
|
308
319
|
## Test summary
|
|
309
320
|
|
|
310
|
-
- Test result:
|
|
321
|
+
- Test result: 2450 passed, 9 deselected in 41s
|
|
311
322
|
- No flake8 warnings.
|
|
312
323
|
- No mypy errors found.
|
|
324
|
+
- No pylint warnings.
|
|
313
325
|
- No python layout warnings.
|
|
314
|
-
- Built version(s): 0.
|
|
326
|
+
- Built version(s): 0.5
|
|
315
327
|
- Build and test using Python 3.14.6
|
|
@@ -14,6 +14,14 @@ 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
|
+
The three packages share a version number and are released together.
|
|
20
|
+
|
|
21
|
+
The library is not meant to ever be "finished": using it for real planning keeps
|
|
22
|
+
surfacing new ideas. Some of those will be added to backlogops itself, and
|
|
23
|
+
others will fit better in separate packages built on top of backlogops.
|
|
24
|
+
|
|
17
25
|
## Available functionality
|
|
18
26
|
|
|
19
27
|
The following functionality is available in all 3 packages:
|
|
@@ -215,6 +223,9 @@ backlogops_cli serves 2 purposes:
|
|
|
215
223
|
python3 -m backlogops_cli.demo_backlog
|
|
216
224
|
Write a demonstration backlog and releases to a file
|
|
217
225
|
|
|
226
|
+
python3 -m backlogops_cli.encrypt_token_file
|
|
227
|
+
Encrypt a Jira API token file with a pass phrase.
|
|
228
|
+
|
|
218
229
|
python3 -m backlogops_cli.estimate_ready_date
|
|
219
230
|
Estimate ready dates for the backlog items
|
|
220
231
|
|
|
@@ -273,9 +284,10 @@ backlogops_cli serves 2 purposes:
|
|
|
273
284
|
|
|
274
285
|
## Test summary
|
|
275
286
|
|
|
276
|
-
- Test result:
|
|
287
|
+
- Test result: 2450 passed, 9 deselected in 41s
|
|
277
288
|
- No flake8 warnings.
|
|
278
289
|
- No mypy errors found.
|
|
290
|
+
- No pylint warnings.
|
|
279
291
|
- No python layout warnings.
|
|
280
|
-
- Built version(s): 0.
|
|
292
|
+
- Built version(s): 0.5
|
|
281
293
|
- Build and test using Python 3.14.6
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: backlogops-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5
|
|
4
4
|
Summary: Command line 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: Environment :: Console
|
|
19
19
|
Classifier: Intended Audience :: Developers
|
|
20
20
|
Classifier: Intended Audience :: End Users/Desktop
|
|
@@ -25,7 +25,7 @@ Requires-Python: >=3.12
|
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
License-File: LICENSE.txt
|
|
27
27
|
Requires-Dist: argcomplete>=3.7.0
|
|
28
|
-
Requires-Dist: backlogops>=0.
|
|
28
|
+
Requires-Dist: backlogops>=0.5
|
|
29
29
|
Requires-Dist: versionreporter>=0.4
|
|
30
30
|
Dynamic: author
|
|
31
31
|
Dynamic: license-file
|
|
@@ -48,6 +48,14 @@ There are 3 related packages for backlog operations:
|
|
|
48
48
|
interface to use the functions in the library. It is based on TkInter. The
|
|
49
49
|
ambition is to keep it as a thin wrapper around the library.
|
|
50
50
|
|
|
51
|
+
## Project status
|
|
52
|
+
|
|
53
|
+
The three packages share a version number and are released together.
|
|
54
|
+
|
|
55
|
+
The library is not meant to ever be "finished": using it for real planning keeps
|
|
56
|
+
surfacing new ideas. Some of those will be added to backlogops itself, and
|
|
57
|
+
others will fit better in separate packages built on top of backlogops.
|
|
58
|
+
|
|
51
59
|
## Available functionality
|
|
52
60
|
|
|
53
61
|
The following functionality is available in all 3 packages:
|
|
@@ -249,6 +257,9 @@ backlogops_cli serves 2 purposes:
|
|
|
249
257
|
python3 -m backlogops_cli.demo_backlog
|
|
250
258
|
Write a demonstration backlog and releases to a file
|
|
251
259
|
|
|
260
|
+
python3 -m backlogops_cli.encrypt_token_file
|
|
261
|
+
Encrypt a Jira API token file with a pass phrase.
|
|
262
|
+
|
|
252
263
|
python3 -m backlogops_cli.estimate_ready_date
|
|
253
264
|
Estimate ready dates for the backlog items
|
|
254
265
|
|
|
@@ -307,9 +318,10 @@ backlogops_cli serves 2 purposes:
|
|
|
307
318
|
|
|
308
319
|
## Test summary
|
|
309
320
|
|
|
310
|
-
- Test result:
|
|
321
|
+
- Test result: 2450 passed, 9 deselected in 41s
|
|
311
322
|
- No flake8 warnings.
|
|
312
323
|
- No mypy errors found.
|
|
324
|
+
- No pylint warnings.
|
|
313
325
|
- No python layout warnings.
|
|
314
|
-
- Built version(s): 0.
|
|
326
|
+
- Built version(s): 0.5
|
|
315
327
|
- Build and test using Python 3.14.6
|
|
@@ -19,6 +19,7 @@ src/backlogops_cli/bloc_version_reporter.py
|
|
|
19
19
|
src/backlogops_cli/config_wizard.py
|
|
20
20
|
src/backlogops_cli/convert.py
|
|
21
21
|
src/backlogops_cli/demo_backlog.py
|
|
22
|
+
src/backlogops_cli/encrypt_token_file.py
|
|
22
23
|
src/backlogops_cli/estimate_ready_date.py
|
|
23
24
|
src/backlogops_cli/extract_keys.py
|
|
24
25
|
src/backlogops_cli/jira_fields.py
|
|
@@ -4,7 +4,6 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "backlogops-cli"
|
|
7
|
-
version = "0.3"
|
|
8
7
|
authors = [
|
|
9
8
|
{ name="Tom Björkholm", email="klausuler_linnet0q@icloud.com" },
|
|
10
9
|
]
|
|
@@ -22,7 +21,7 @@ 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
|
"Environment :: Console",
|
|
27
26
|
"Intended Audience :: Developers",
|
|
28
27
|
"Intended Audience :: End Users/Desktop",
|
|
@@ -30,7 +29,7 @@ classifiers = [
|
|
|
30
29
|
"Topic :: Office/Business :: Scheduling",
|
|
31
30
|
"Typing :: Typed"
|
|
32
31
|
]
|
|
33
|
-
dynamic = ["dependencies"]
|
|
32
|
+
dynamic = ["dependencies", "version"]
|
|
34
33
|
|
|
35
34
|
[project.urls]
|
|
36
35
|
"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-cli',
|
|
8
|
-
version='0.
|
|
8
|
+
version='0.5',
|
|
9
9
|
description='Command line interface for backlog operations.',
|
|
10
10
|
author='Tom Björkholm',
|
|
11
11
|
author_email='klausuler_linnet0q@icloud.com',
|
|
@@ -15,7 +15,7 @@ setup(
|
|
|
15
15
|
package_data={'backlogops_cli': ['src/py.typed']},
|
|
16
16
|
install_requires=[
|
|
17
17
|
'argcomplete >= 3.7.0',
|
|
18
|
-
'backlogops >= 0.
|
|
18
|
+
'backlogops >= 0.5',
|
|
19
19
|
'versionreporter >= 0.4'
|
|
20
20
|
]
|
|
21
21
|
)
|
|
@@ -12,9 +12,10 @@ underscore in the module name keeps it out of the command listing.
|
|
|
12
12
|
|
|
13
13
|
import argparse
|
|
14
14
|
import sys
|
|
15
|
+
from getpass import getpass
|
|
15
16
|
from pathlib import Path
|
|
16
17
|
from collections.abc import Callable
|
|
17
|
-
from typing import Optional, TextIO
|
|
18
|
+
from typing import Optional, TextIO, TypeVar
|
|
18
19
|
import argcomplete
|
|
19
20
|
from backlogops_cli._migrate_warn import (
|
|
20
21
|
CliMigrateWarnHook, CliPresetMigrateWarnHook)
|
|
@@ -27,6 +28,9 @@ from backlogops import (
|
|
|
27
28
|
resolve_output_config, write_backlog_releases, write_content_changes,
|
|
28
29
|
write_date_changes)
|
|
29
30
|
|
|
31
|
+
ResultT = TypeVar('ResultT')
|
|
32
|
+
"""Result type of a Jira write, carried from the operation to the report."""
|
|
33
|
+
|
|
30
34
|
RANK_ANCHOR_CHOICES = {
|
|
31
35
|
'backlog-top': JiraRankAnchor.BACKLOG_TOP,
|
|
32
36
|
'backlog-bottom': JiraRankAnchor.BACKLOG_BOTTOM,
|
|
@@ -97,6 +101,11 @@ def parsed_args(parser: argparse.ArgumentParser,
|
|
|
97
101
|
return parser.parse_args(args)
|
|
98
102
|
|
|
99
103
|
|
|
104
|
+
def jira_passphrase() -> str:
|
|
105
|
+
"""Ask for the Jira API token pass phrase on the terminal."""
|
|
106
|
+
return getpass('Jira API token pass phrase: ')
|
|
107
|
+
|
|
108
|
+
|
|
100
109
|
def add_input_args(parser: argparse.ArgumentParser) -> None:
|
|
101
110
|
"""Add the input-file and input-config arguments."""
|
|
102
111
|
parser.add_argument('-i', '--input', dest='input', required=True,
|
|
@@ -120,6 +129,18 @@ def add_config_arg(parser: argparse.ArgumentParser) -> None:
|
|
|
120
129
|
'$HOME/.backlogops.cfg.')
|
|
121
130
|
|
|
122
131
|
|
|
132
|
+
def add_preset_arg(parser: argparse.ArgumentParser) -> None:
|
|
133
|
+
"""Add the required ``-p``/``--preset`` Jira preset argument."""
|
|
134
|
+
parser.add_argument('-p', '--preset', dest='preset', required=True,
|
|
135
|
+
help='Name of the Jira preset in the configuration.')
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def add_quiet_arg(parser: argparse.ArgumentParser) -> None:
|
|
139
|
+
"""Add the ``-q``/``--quiet`` flag suppressing the result lists."""
|
|
140
|
+
parser.add_argument('-q', '--quiet', dest='quiet', action='store_true',
|
|
141
|
+
help='Do not print the result lists to stdout.')
|
|
142
|
+
|
|
143
|
+
|
|
123
144
|
def _resolve_config(parsed: argparse.Namespace) -> BacklogOpsConfig:
|
|
124
145
|
"""Return the backlog-ops configuration from ``-c`` or by discovery.
|
|
125
146
|
|
|
@@ -265,6 +286,23 @@ def build_io_parser(description: str, *, with_input: bool = True,
|
|
|
265
286
|
return parser
|
|
266
287
|
|
|
267
288
|
|
|
289
|
+
def build_jira_parser(description: str, *,
|
|
290
|
+
with_input: bool = True) -> argparse.ArgumentParser:
|
|
291
|
+
"""Create a parser with the config, preset and optional input options.
|
|
292
|
+
|
|
293
|
+
The Jira commands all take a backlog-ops configuration and a required
|
|
294
|
+
``-p``/``--preset``. Most also read an input file, so ``with_input``
|
|
295
|
+
adds the input-file and input-config options by default; a command
|
|
296
|
+
that does not read a backlog file passes ``with_input=False``.
|
|
297
|
+
"""
|
|
298
|
+
parser = argparse.ArgumentParser(description=description)
|
|
299
|
+
if with_input:
|
|
300
|
+
add_input_args(parser)
|
|
301
|
+
add_config_arg(parser)
|
|
302
|
+
add_preset_arg(parser)
|
|
303
|
+
return parser
|
|
304
|
+
|
|
305
|
+
|
|
268
306
|
def _write_output(parsed: argparse.Namespace,
|
|
269
307
|
config: Optional[BacklogOpsConfig],
|
|
270
308
|
data: BacklogReleases) -> None:
|
|
@@ -317,6 +355,63 @@ def run_write(parsed: argparse.Namespace,
|
|
|
317
355
|
return 0
|
|
318
356
|
|
|
319
357
|
|
|
358
|
+
def run_write_command(
|
|
359
|
+
build_parser: Callable[[], argparse.ArgumentParser],
|
|
360
|
+
produce: Callable[[argparse.Namespace, Optional[BacklogOpsConfig]],
|
|
361
|
+
BacklogReleases],
|
|
362
|
+
args: Optional[list[str]]) -> int:
|
|
363
|
+
"""Parse the command line and write the produced backlog and releases.
|
|
364
|
+
|
|
365
|
+
Folds argument parsing and :func:`run_write` so an ordering command's
|
|
366
|
+
``main`` stays a single delegating call. ``produce`` receives the
|
|
367
|
+
parsed args and the resolved config (or None) and returns the data.
|
|
368
|
+
"""
|
|
369
|
+
parsed = parsed_args(build_parser(), args)
|
|
370
|
+
return run_write(parsed, lambda config: produce(parsed, config))
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def write_result_file(config: BacklogOpsConfig, path: str,
|
|
374
|
+
data: BacklogReleases, force: bool) -> None:
|
|
375
|
+
"""Write one backlog-and-releases result file, honoring ``--force``."""
|
|
376
|
+
out_config = resolve_output_config(None, data_file=path,
|
|
377
|
+
presets=config.output_configs,
|
|
378
|
+
auto_ch_hook=CliPresetMigrateWarnHook())
|
|
379
|
+
write_backlog_releases(data, path, out_config, FormatRules(),
|
|
380
|
+
levels=config.get_levels(),
|
|
381
|
+
file_exists_callback=overwrite_callback(force))
|
|
382
|
+
print(f'Wrote {path}')
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def run_added_to_jira(
|
|
386
|
+
parsed: argparse.Namespace,
|
|
387
|
+
add: Callable[
|
|
388
|
+
[argparse.Namespace, BacklogOpsConfig, BacklogReleases], ResultT],
|
|
389
|
+
report: Callable[[ResultT], str], exists_error: type[Exception],
|
|
390
|
+
fail_note: str) -> int:
|
|
391
|
+
"""Read the input, add it to Jira, and print the added lists.
|
|
392
|
+
|
|
393
|
+
``add`` receives the parsed args, the config and the input data, adds
|
|
394
|
+
to Jira, writes any ``--added``/``--existing`` files, and returns the
|
|
395
|
+
result. ``report`` formats that result for stdout, printed unless
|
|
396
|
+
``-q``/``--quiet`` was given. ``exists_error`` is the key-exists error
|
|
397
|
+
meaning nothing was added; other value or OS errors are reported with
|
|
398
|
+
``fail_note``.
|
|
399
|
+
"""
|
|
400
|
+
try:
|
|
401
|
+
config = required_config(parsed)
|
|
402
|
+
data = read_input(parsed, config)
|
|
403
|
+
result = add(parsed, config, data)
|
|
404
|
+
except exists_error:
|
|
405
|
+
print('Nothing added to Jira.', file=sys.stderr)
|
|
406
|
+
return 1
|
|
407
|
+
except (ValueError, TypeError, KeyError, OSError) as error:
|
|
408
|
+
print(f'{fail_note}: {error}', file=sys.stderr)
|
|
409
|
+
return 1
|
|
410
|
+
if not parsed.quiet:
|
|
411
|
+
print(report(result))
|
|
412
|
+
return 0
|
|
413
|
+
|
|
414
|
+
|
|
320
415
|
DEFAULT_BUFFER_DAYS = 5
|
|
321
416
|
"""Default slack in calendar days added when fitting dates."""
|
|
322
417
|
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#! /usr/local/bin/python3
|
|
2
|
+
"""Shared helpers for the configuration and preset wizard commands.
|
|
3
|
+
|
|
4
|
+
Both wizard commands write a JSON configuration file built interactively
|
|
5
|
+
through a ``WizardUiBridge``. They share the same command line shape (an
|
|
6
|
+
output file, an optional input file whose contents pre-fill the wizard, a
|
|
7
|
+
switch forcing the plain console interface, and a force flag), the same
|
|
8
|
+
overwrite check, the same read-run-write-report flow, and the same
|
|
9
|
+
crash-safe write. That shared logic lives here; the leading underscore in
|
|
10
|
+
the module name keeps it out of the command listing.
|
|
11
|
+
"""
|
|
12
|
+
# PYTHON_ARGCOMPLETE_OK
|
|
13
|
+
|
|
14
|
+
# Copyright (c) 2026, Tom Björkholm
|
|
15
|
+
# MIT License
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import sys
|
|
19
|
+
from collections.abc import Callable
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Optional, TypeVar
|
|
22
|
+
from config_as_json import Config
|
|
23
|
+
from config_as_json.file_extension import fix_file_extension
|
|
24
|
+
from tableio_cfg_json import WizardUiBridge, WizardUiBridgeConsole, \
|
|
25
|
+
make_text_ui_bridge
|
|
26
|
+
from backlogops import safe_write_config
|
|
27
|
+
from backlogops_cli._command_io import add_force_arg, overwrite_callback
|
|
28
|
+
|
|
29
|
+
CONFIG_EXTENSION = '.cfg'
|
|
30
|
+
_ConfigT = TypeVar('_ConfigT', bound=Config)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def build_wizard_parser(description: str) -> argparse.ArgumentParser:
|
|
34
|
+
"""Build a wizard parser with output, input, no-textual and force."""
|
|
35
|
+
parser = argparse.ArgumentParser(description=description)
|
|
36
|
+
parser.add_argument('-o', '--output', dest='output', required=True,
|
|
37
|
+
help='Configuration file to write; the '
|
|
38
|
+
f'{CONFIG_EXTENSION} extension is added if missing.')
|
|
39
|
+
parser.add_argument('-i', '--input', dest='input',
|
|
40
|
+
help='Existing configuration file whose contents '
|
|
41
|
+
'pre-fill the wizard as the starting answers, so you '
|
|
42
|
+
'edit them instead of starting empty. Use the same '
|
|
43
|
+
'path as -o to edit that file in place; you are then '
|
|
44
|
+
f'asked to confirm overwriting it. The '
|
|
45
|
+
f'{CONFIG_EXTENSION} extension is assumed when the '
|
|
46
|
+
'file is not found as named.')
|
|
47
|
+
parser.add_argument('--no-textual', dest='no_textual', action='store_true',
|
|
48
|
+
help='Force the plain console interface instead of '
|
|
49
|
+
'the Textual full-screen interface.')
|
|
50
|
+
add_force_arg(parser)
|
|
51
|
+
return parser
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _check_overwrite(output: str, force: bool) -> None:
|
|
55
|
+
"""Ask before overwriting an existing configuration file.
|
|
56
|
+
|
|
57
|
+
The check runs before the wizard, so the user is not asked to confirm
|
|
58
|
+
an overwrite only after entering the whole configuration.
|
|
59
|
+
"""
|
|
60
|
+
if Path(output).exists():
|
|
61
|
+
overwrite_callback(force)(output)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _make_bridge(no_textual: bool) -> WizardUiBridge:
|
|
65
|
+
"""Return the console bridge when forced, else the best text bridge.
|
|
66
|
+
|
|
67
|
+
Without ``--no-textual`` the factory returns a Textual full-screen
|
|
68
|
+
bridge in a real terminal and a console bridge otherwise, such as when
|
|
69
|
+
input is redirected or under tests.
|
|
70
|
+
"""
|
|
71
|
+
if no_textual:
|
|
72
|
+
return WizardUiBridgeConsole(sys.stdout, sys.stdin, sys.stderr)
|
|
73
|
+
return make_text_ui_bridge(sys.stdout, sys.stdin, sys.stderr)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _read_default(input_file: Optional[str],
|
|
77
|
+
reader: Callable[[str], _ConfigT]) -> Optional[_ConfigT]:
|
|
78
|
+
"""Read the pre-fill file, or return None when none is requested.
|
|
79
|
+
|
|
80
|
+
The ``.cfg`` extension is assumed when the named file is not found as
|
|
81
|
+
given, matching how the output filename is completed.
|
|
82
|
+
"""
|
|
83
|
+
if input_file is None:
|
|
84
|
+
return None
|
|
85
|
+
path = fix_file_extension(input_file, CONFIG_EXTENSION, for_reading=True)
|
|
86
|
+
if not Path(path).is_file():
|
|
87
|
+
raise ValueError(f'File to read initial values from not found: {path}')
|
|
88
|
+
return reader(path)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def run_wizard_to_file(parsed: argparse.Namespace,
|
|
92
|
+
wizard: Callable[..., _ConfigT],
|
|
93
|
+
reader: Callable[[str], _ConfigT], label: str) -> int:
|
|
94
|
+
"""Run a wizard, write its configuration to the output file, report.
|
|
95
|
+
|
|
96
|
+
When ``parsed.input`` names an existing file it is read first and used
|
|
97
|
+
to pre-fill the wizard, so the user edits those values instead of
|
|
98
|
+
starting from scratch. The output filename receives the ``.cfg``
|
|
99
|
+
extension when it is not already present, an existing output file is
|
|
100
|
+
only overwritten after confirmation (or with ``--force``), and the
|
|
101
|
+
result is written crash-safely through a ``.in_progress`` sibling file.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
parsed: Parsed arguments holding ``output``, ``input``, ``force``
|
|
105
|
+
and ``no_textual``.
|
|
106
|
+
wizard: Wizard called with the chosen UI bridge and the pre-fill
|
|
107
|
+
default; it returns a configuration object that knows how to
|
|
108
|
+
write itself.
|
|
109
|
+
reader: Reads the ``input`` file into a default for the wizard.
|
|
110
|
+
label: Human-readable name of what was written, for the message.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
``0`` on success, ``1`` when the wizard is abandoned or the
|
|
114
|
+
configuration is rejected or cannot be read or written.
|
|
115
|
+
"""
|
|
116
|
+
output = fix_file_extension(parsed.output, CONFIG_EXTENSION)
|
|
117
|
+
try:
|
|
118
|
+
default = _read_default(parsed.input, reader)
|
|
119
|
+
_check_overwrite(output, parsed.force)
|
|
120
|
+
config = wizard(_make_bridge(parsed.no_textual), default=default)
|
|
121
|
+
safe_write_config(config, output, sys.stderr)
|
|
122
|
+
except (ValueError, TypeError, KeyError, EOFError, OSError) as error:
|
|
123
|
+
print(f'Could not create the configuration: {error}', file=sys.stderr)
|
|
124
|
+
return 1
|
|
125
|
+
print(f'{label} written to {output}')
|
|
126
|
+
return 0
|
|
@@ -20,27 +20,21 @@ asked on the terminal only when it is needed.
|
|
|
20
20
|
|
|
21
21
|
import argparse
|
|
22
22
|
import sys
|
|
23
|
-
from getpass import getpass
|
|
24
23
|
from typing import Optional
|
|
25
24
|
from backlogops import (
|
|
26
|
-
AddedReleasesToJira, BacklogOpsConfig, BacklogReleases,
|
|
27
|
-
|
|
28
|
-
format_release_result
|
|
25
|
+
AddedReleasesToJira, BacklogOpsConfig, BacklogReleases, JiraConnections,
|
|
26
|
+
OnExistingKey, ReleaseExistsError, add_releases_to_jira,
|
|
27
|
+
format_release_result)
|
|
29
28
|
from backlogops_cli._command_io import (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
from backlogops_cli._migrate_warn import CliPresetMigrateWarnHook
|
|
29
|
+
add_force_arg, add_quiet_arg, build_jira_parser, jira_passphrase,
|
|
30
|
+
parsed_args, run_added_to_jira, write_result_file)
|
|
33
31
|
|
|
34
32
|
DESCRIPTION = 'Add releases to Jira, creating a new version per release'
|
|
35
33
|
|
|
36
34
|
|
|
37
35
|
def build_parser() -> argparse.ArgumentParser:
|
|
38
36
|
"""Build the command line parser for the add-releases command."""
|
|
39
|
-
parser =
|
|
40
|
-
add_input_args(parser)
|
|
41
|
-
add_config_arg(parser)
|
|
42
|
-
parser.add_argument('-p', '--preset', dest='preset', required=True,
|
|
43
|
-
help='Name of the Jira preset in the configuration.')
|
|
37
|
+
parser = build_jira_parser(DESCRIPTION)
|
|
44
38
|
parser.add_argument('--skip-existing', dest='skip_existing',
|
|
45
39
|
action='store_true',
|
|
46
40
|
help='Skip releases whose name already exists in Jira '
|
|
@@ -52,26 +46,17 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
52
46
|
metavar='FILE',
|
|
53
47
|
help='Write the releases already in Jira, with the '
|
|
54
48
|
'input backlog, to this file. Omit to not write it.')
|
|
55
|
-
parser
|
|
56
|
-
|
|
57
|
-
'lists to stdout.')
|
|
58
|
-
parser.add_argument('-f', '--force', dest='force', action='store_true',
|
|
59
|
-
help='Overwrite existing output files without '
|
|
60
|
-
'asking.')
|
|
49
|
+
add_quiet_arg(parser)
|
|
50
|
+
add_force_arg(parser)
|
|
61
51
|
return parser
|
|
62
52
|
|
|
63
53
|
|
|
64
|
-
def _passphrase() -> str:
|
|
65
|
-
"""Ask for the Jira token pass phrase on the terminal."""
|
|
66
|
-
return getpass('Jira API token pass phrase: ')
|
|
67
|
-
|
|
68
|
-
|
|
69
54
|
def _add(parsed: argparse.Namespace, config: BacklogOpsConfig,
|
|
70
55
|
data: BacklogReleases) -> AddedReleasesToJira:
|
|
71
56
|
"""Add the input releases to Jira using the named preset."""
|
|
72
57
|
print(f"Adding releases to Jira using preset '{parsed.preset}'...",
|
|
73
58
|
file=sys.stderr)
|
|
74
|
-
connections = JiraConnections(config.get_jira_config(),
|
|
59
|
+
connections = JiraConnections(config.get_jira_config(), jira_passphrase)
|
|
75
60
|
mode = (OnExistingKey.SKIP if parsed.skip_existing
|
|
76
61
|
else OnExistingKey.RAISE)
|
|
77
62
|
result = add_releases_to_jira(connections, parsed.preset, data.releases,
|
|
@@ -82,18 +67,6 @@ def _add(parsed: argparse.Namespace, config: BacklogOpsConfig,
|
|
|
82
67
|
return result
|
|
83
68
|
|
|
84
69
|
|
|
85
|
-
def _write_file(config: BacklogOpsConfig, path: str, data: BacklogReleases,
|
|
86
|
-
force: bool) -> None:
|
|
87
|
-
"""Write one backlog-and-releases result to a file."""
|
|
88
|
-
out_config = resolve_output_config(None, data_file=path,
|
|
89
|
-
presets=config.output_configs,
|
|
90
|
-
auto_ch_hook=CliPresetMigrateWarnHook())
|
|
91
|
-
write_backlog_releases(data, path, out_config, FormatRules(),
|
|
92
|
-
levels=config.get_levels(),
|
|
93
|
-
file_exists_callback=overwrite_callback(force))
|
|
94
|
-
print(f'Wrote {path}')
|
|
95
|
-
|
|
96
|
-
|
|
97
70
|
def _write_result_files(parsed: argparse.Namespace, config: BacklogOpsConfig,
|
|
98
71
|
data: BacklogReleases,
|
|
99
72
|
result: AddedReleasesToJira) -> None:
|
|
@@ -101,29 +74,26 @@ def _write_result_files(parsed: argparse.Namespace, config: BacklogOpsConfig,
|
|
|
101
74
|
backlog = list(data.backlog)
|
|
102
75
|
if parsed.added_file is not None:
|
|
103
76
|
added = BacklogReleases(backlog=backlog, releases=result.stored)
|
|
104
|
-
|
|
77
|
+
write_result_file(config, parsed.added_file, added, parsed.force)
|
|
105
78
|
if parsed.existing_file is not None:
|
|
106
79
|
present = BacklogReleases(backlog=backlog,
|
|
107
80
|
releases=result.already_present)
|
|
108
|
-
|
|
81
|
+
write_result_file(config, parsed.existing_file, present, parsed.force)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _add_and_write(parsed: argparse.Namespace, config: BacklogOpsConfig,
|
|
85
|
+
data: BacklogReleases) -> AddedReleasesToJira:
|
|
86
|
+
"""Add the releases to Jira and write any requested result files."""
|
|
87
|
+
result = _add(parsed, config, data)
|
|
88
|
+
_write_result_files(parsed, config, data, result)
|
|
89
|
+
return result
|
|
109
90
|
|
|
110
91
|
|
|
111
92
|
def _run(parsed: argparse.Namespace) -> int:
|
|
112
93
|
"""Read the input, add the releases, write files and print the lists."""
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
result = _add(parsed, config, data)
|
|
117
|
-
_write_result_files(parsed, config, data, result)
|
|
118
|
-
except ReleaseExistsError:
|
|
119
|
-
print('Nothing added to Jira.', file=sys.stderr)
|
|
120
|
-
return 1
|
|
121
|
-
except (ValueError, TypeError, KeyError, OSError) as error:
|
|
122
|
-
print(f'Could not add releases to Jira: {error}', file=sys.stderr)
|
|
123
|
-
return 1
|
|
124
|
-
if not parsed.quiet:
|
|
125
|
-
print(format_release_result(result))
|
|
126
|
-
return 0
|
|
94
|
+
return run_added_to_jira(parsed, _add_and_write, format_release_result,
|
|
95
|
+
ReleaseExistsError,
|
|
96
|
+
'Could not add releases to Jira')
|
|
127
97
|
|
|
128
98
|
|
|
129
99
|
def main(args: Optional[list[str]] = None) -> int:
|
|
@@ -136,8 +106,7 @@ def main(args: Optional[list[str]] = None) -> int:
|
|
|
136
106
|
``0`` on success, ``1`` when the releases cannot be added or a name
|
|
137
107
|
already exists in Jira without ``--skip-existing``.
|
|
138
108
|
"""
|
|
139
|
-
|
|
140
|
-
return _run(parsed)
|
|
109
|
+
return _run(parsed_args(build_parser(), args))
|
|
141
110
|
|
|
142
111
|
|
|
143
112
|
if __name__ == '__main__': # pragma: no cover
|