kd-cli 2.0.6__tar.gz → 2.1.0__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.
- {kd_cli-2.0.6 → kd_cli-2.1.0}/MANIFEST.in +1 -1
- {kd_cli-2.0.6/kd_cli.egg-info → kd_cli-2.1.0}/PKG-INFO +5 -1
- {kd_cli-2.0.6 → kd_cli-2.1.0}/README.md +2 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/main.py +22 -6
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/namespaces/__init__.py +2 -0
- kd_cli-2.1.0/kd/cli/namespaces/init.py +61 -0
- kd_cli-2.1.0/kd/cli/namespaces/release_notes.py +116 -0
- kd_cli-2.1.0/kd/cli/namespaces/system/__init__.py +1161 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/config.py +544 -109
- kd_cli-2.1.0/kd/config_schema.py +266 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/connection.py +44 -12
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/exceptions.py +4 -0
- kd_cli-2.1.0/kd/release_notes.py +111 -0
- kd_cli-2.1.0/kd/resources/CHANGELOG.md +54 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/resources/config-template.yaml +3 -15
- {kd_cli-2.0.6 → kd_cli-2.1.0/kd_cli.egg-info}/PKG-INFO +5 -1
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd_cli.egg-info/SOURCES.txt +4 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd_cli.egg-info/requires.txt +1 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/pyproject.toml +4 -2
- kd_cli-2.0.6/kd/cli/namespaces/init.py +0 -42
- kd_cli-2.0.6/kd/cli/namespaces/system/__init__.py +0 -528
- {kd_cli-2.0.6 → kd_cli-2.1.0}/LICENSE +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/__init__.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/api/__init__.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/api/articles.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/api/http.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/api/hub.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/api/saved_queries.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/api/tags.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/api/youtrack.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/article_marker.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/auth.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/__init__.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/core/__init__.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/core/command_base.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/core/command_parser.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/core/namespaces.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/core/option_registry.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/input.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/namespaces/alias/__init__.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/namespaces/article/__init__.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/namespaces/issue/__init__.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/namespaces/primer/__init__.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/namespaces/project/__init__.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/namespaces/query/__init__.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/namespaces/search/__init__.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/namespaces/tag/__init__.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/namespaces/time/__init__.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/namespaces/user/__init__.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/output.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/cli/tree.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/client.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/debug.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/history.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/models/__init__.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/models/article.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/models/assist.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/models/attachment.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/models/comment.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/models/common.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/models/issue.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/models/project.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/models/saved_query.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/models/search.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/models/tag.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/models/user.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/models/work_item.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/primer.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/query_parser.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/resources/__init__.py +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/resources/command-synonyms.yaml +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd/resources/error-messages.yaml +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd_cli.egg-info/dependency_links.txt +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd_cli.egg-info/entry_points.txt +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/kd_cli.egg-info/top_level.txt +0 -0
- {kd_cli-2.0.6 → kd_cli-2.1.0}/setup.cfg +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kd-cli
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: Knowledge Dispatcher for issue trackers and knowledge bases
|
|
5
5
|
Author: Sergiy Gladkyy
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://pypi.org/project/kd-cli/
|
|
8
8
|
Project-URL: Issues, https://github.com/IntellectSolutionsCom/kd-cli/issues
|
|
9
9
|
Project-URL: Source, https://github.com/IntellectSolutionsCom/kd-cli
|
|
10
|
+
Project-URL: Changelog, https://github.com/IntellectSolutionsCom/kd-cli/blob/main/kd/resources/CHANGELOG.md
|
|
10
11
|
Keywords: youtrack,cli,sdk,knowledge-management,knowledge-network
|
|
11
12
|
Classifier: Development Status :: 4 - Beta
|
|
12
13
|
Classifier: Environment :: Console
|
|
@@ -22,6 +23,7 @@ Requires-Python: >=3.11
|
|
|
22
23
|
Description-Content-Type: text/markdown
|
|
23
24
|
License-File: LICENSE
|
|
24
25
|
Requires-Dist: httpx>=0.27
|
|
26
|
+
Requires-Dist: packaging>=23.0
|
|
25
27
|
Requires-Dist: pydantic>=2.0
|
|
26
28
|
Requires-Dist: pyyaml>=6.0
|
|
27
29
|
Requires-Dist: tabulate>=0.9
|
|
@@ -80,6 +82,8 @@ uv tool install kd-cli
|
|
|
80
82
|
|
|
81
83
|
The PyPI distribution name is `kd-cli` (two-letter `kd` is reserved by PyPI policy). The installed command, the import name, and the CLI entry point are all `kd`.
|
|
82
84
|
|
|
85
|
+
Release notes for the installed version: `kd /release-notes`. The canonical changelog lives at [`kd/resources/CHANGELOG.md`](kd/resources/CHANGELOG.md).
|
|
86
|
+
|
|
83
87
|
## Configure
|
|
84
88
|
|
|
85
89
|
```bash
|
|
@@ -43,6 +43,8 @@ uv tool install kd-cli
|
|
|
43
43
|
|
|
44
44
|
The PyPI distribution name is `kd-cli` (two-letter `kd` is reserved by PyPI policy). The installed command, the import name, and the CLI entry point are all `kd`.
|
|
45
45
|
|
|
46
|
+
Release notes for the installed version: `kd /release-notes`. The canonical changelog lives at [`kd/resources/CHANGELOG.md`](kd/resources/CHANGELOG.md).
|
|
47
|
+
|
|
46
48
|
## Configure
|
|
47
49
|
|
|
48
50
|
```bash
|
|
@@ -29,7 +29,7 @@ from kd.client import YouTrackClient
|
|
|
29
29
|
from kd.config import ResolvedConfig, resolve_command_defaults, resolve_config
|
|
30
30
|
from kd.connection import ConnectionContext, resolve_connection
|
|
31
31
|
from kd.debug import DebugLevel, DebugLog, init_debug_log
|
|
32
|
-
from kd.exceptions import APIError, KdError
|
|
32
|
+
from kd.exceptions import APIError, ConfigError, KdError
|
|
33
33
|
|
|
34
34
|
# Global options — single source of truth for extraction, help, and reference.
|
|
35
35
|
GLOBAL_OPTIONS: list[OptionDeclaration] = [
|
|
@@ -121,10 +121,26 @@ def expand_toplevel_alias(
|
|
|
121
121
|
def _print_error(error: KdError) -> None:
|
|
122
122
|
"""Print error with optional suggestion to stderr."""
|
|
123
123
|
print(f"Error: {error}", file=sys.stderr)
|
|
124
|
-
if isinstance(error, APIError) and error.suggestion:
|
|
124
|
+
if isinstance(error, (APIError, ConfigError)) and error.suggestion:
|
|
125
125
|
print(f"Hint: {error.suggestion}", file=sys.stderr)
|
|
126
126
|
|
|
127
127
|
|
|
128
|
+
def _resolve_config_or_exit(
|
|
129
|
+
flags: dict[str, str] | None,
|
|
130
|
+
debug_log: DebugLog,
|
|
131
|
+
) -> ResolvedConfig:
|
|
132
|
+
"""Wrap :func:`resolve_config` so config failures exit cleanly.
|
|
133
|
+
|
|
134
|
+
Unexpected (non-``KdError``) exceptions are not caught — they must
|
|
135
|
+
remain visible with their traceback.
|
|
136
|
+
"""
|
|
137
|
+
try:
|
|
138
|
+
return resolve_config(flags=flags, debug_log=debug_log)
|
|
139
|
+
except KdError as e:
|
|
140
|
+
_print_error(e)
|
|
141
|
+
sys.exit(1)
|
|
142
|
+
|
|
143
|
+
|
|
128
144
|
def main(argv: list[str] | None = None) -> None:
|
|
129
145
|
"""CLI entry point."""
|
|
130
146
|
args = argv if argv is not None else sys.argv[1:]
|
|
@@ -146,7 +162,7 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
146
162
|
# Config is loaded early only when needed; stored for reuse later.
|
|
147
163
|
resolved: ResolvedConfig | None = None
|
|
148
164
|
if len(remaining) >= 2 and remaining[0] == "alias" and remaining[1] not in _ALIAS_SUBCOMMANDS:
|
|
149
|
-
resolved =
|
|
165
|
+
resolved = _resolve_config_or_exit(config_flags, debug_log)
|
|
150
166
|
remaining = expand_alias(remaining, resolved)
|
|
151
167
|
|
|
152
168
|
# Build registry and parser
|
|
@@ -155,7 +171,7 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
155
171
|
|
|
156
172
|
if global_opts.get("reference"):
|
|
157
173
|
if resolved is None:
|
|
158
|
-
resolved =
|
|
174
|
+
resolved = _resolve_config_or_exit(config_flags, debug_log)
|
|
159
175
|
_show_reference(registry, config=resolved)
|
|
160
176
|
return
|
|
161
177
|
parser = CommandParser(registry)
|
|
@@ -195,7 +211,7 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
195
211
|
# check whether it matches a configured alias and expand it.
|
|
196
212
|
if unresolved.namespace is registry.root:
|
|
197
213
|
if resolved is None:
|
|
198
|
-
resolved =
|
|
214
|
+
resolved = _resolve_config_or_exit(config_flags, debug_log)
|
|
199
215
|
expanded = expand_toplevel_alias(unresolved.command_name, remaining[1:], resolved)
|
|
200
216
|
if expanded is not None:
|
|
201
217
|
try:
|
|
@@ -229,7 +245,7 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
229
245
|
|
|
230
246
|
# Resolve config from flags + env + local + global (reuse if loaded for alias expansion)
|
|
231
247
|
if resolved is None:
|
|
232
|
-
resolved =
|
|
248
|
+
resolved = _resolve_config_or_exit(config_flags, debug_log)
|
|
233
249
|
|
|
234
250
|
# Debug config key: if config says debug=true and no -D on CLI, enable level 1
|
|
235
251
|
if resolved.debug and not global_opts.get("debug"):
|
|
@@ -12,6 +12,7 @@ from kd.cli.namespaces.issue import create_issue_namespace
|
|
|
12
12
|
from kd.cli.namespaces.primer import create_primer_namespace
|
|
13
13
|
from kd.cli.namespaces.project import create_project_namespace
|
|
14
14
|
from kd.cli.namespaces.query import create_query_namespace
|
|
15
|
+
from kd.cli.namespaces.release_notes import ReleaseNotesCommand
|
|
15
16
|
from kd.cli.namespaces.search import SearchCommand
|
|
16
17
|
from kd.cli.namespaces.system import create_system_namespace
|
|
17
18
|
from kd.cli.namespaces.tag import create_tag_namespace
|
|
@@ -22,6 +23,7 @@ from kd.cli.namespaces.user import create_user_namespace
|
|
|
22
23
|
def register_all_namespaces(registry: NamespaceRegistry) -> None:
|
|
23
24
|
"""Register all namespace trees in the registry."""
|
|
24
25
|
registry.root.register_command("init", InitCommand())
|
|
26
|
+
registry.root.register_command("release-notes", ReleaseNotesCommand())
|
|
25
27
|
registry.root.register_command("search", SearchCommand())
|
|
26
28
|
registry.register_namespace(create_alias_namespace())
|
|
27
29
|
registry.register_namespace(create_article_namespace())
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Init command: kd init
|
|
2
|
+
|
|
3
|
+
Scaffolds a kd config file at the local tier (.kd/config.yaml in the
|
|
4
|
+
current directory, with .gitignore management) or the global tier
|
|
5
|
+
(~/.config/kd/config.yaml, parent directory created as needed).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from kd.cli.core.command_base import Command, CommandResult
|
|
13
|
+
from kd.cli.core.option_registry import OptionDeclaration, OptionLayer, ResolvedOptions
|
|
14
|
+
from kd.config import init_project_config
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class InitCommand(Command):
|
|
18
|
+
"""Initialize project config."""
|
|
19
|
+
|
|
20
|
+
def __init__(self) -> None:
|
|
21
|
+
super().__init__(
|
|
22
|
+
"init",
|
|
23
|
+
"Initialize kd config (.kd/config.yaml or ~/.config/kd/config.yaml)",
|
|
24
|
+
usage="kd init [URL] [--global]",
|
|
25
|
+
long_description=(
|
|
26
|
+
"Creates a config file. Default is .kd/config.yaml in the "
|
|
27
|
+
"current directory, with .kd/ added to .gitignore when inside "
|
|
28
|
+
"a git repository. With --global (-g), writes "
|
|
29
|
+
"~/.config/kd/config.yaml instead, creating the parent "
|
|
30
|
+
"directory if needed. Optionally pre-fills the YouTrack URL."
|
|
31
|
+
),
|
|
32
|
+
examples=[
|
|
33
|
+
"kd init",
|
|
34
|
+
"kd init https://example.youtrack.cloud",
|
|
35
|
+
"kd init --global https://example.youtrack.cloud",
|
|
36
|
+
],
|
|
37
|
+
see_also=["/system/config list", "/system/config which"],
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def _register_options(self) -> None:
|
|
41
|
+
self.register_option(
|
|
42
|
+
OptionDeclaration(
|
|
43
|
+
name="global",
|
|
44
|
+
layer=OptionLayer.COMMAND,
|
|
45
|
+
type=bool,
|
|
46
|
+
short_name="g",
|
|
47
|
+
help_text="Scaffold global config at ~/.config/kd/config.yaml",
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def execute(
|
|
52
|
+
self, args: list[str], options: ResolvedOptions, client: Any = None
|
|
53
|
+
) -> CommandResult:
|
|
54
|
+
url = args[0] if args else None
|
|
55
|
+
use_global = options.command.get("global", False)
|
|
56
|
+
result = init_project_config(url=url, global_tier=use_global)
|
|
57
|
+
return {
|
|
58
|
+
"created": str(result.created),
|
|
59
|
+
"gitignore": result.gitignore_action,
|
|
60
|
+
"next": "Run 'kd /system/config keys' to list configurable paths.",
|
|
61
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Release-notes command: /release-notes
|
|
2
|
+
|
|
3
|
+
Serves the bundled `kd/resources/CHANGELOG.md`. Supports slicing by
|
|
4
|
+
`--version`, `--since`, or `--all`. Default returns the latest released
|
|
5
|
+
section.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from packaging.version import InvalidVersion
|
|
13
|
+
|
|
14
|
+
from kd.cli.core.command_base import Command, CommandResult
|
|
15
|
+
from kd.cli.core.option_registry import OptionDeclaration, OptionLayer, ResolvedOptions
|
|
16
|
+
from kd.exceptions import ValidationError
|
|
17
|
+
from kd.release_notes import (
|
|
18
|
+
filter_since,
|
|
19
|
+
get_version,
|
|
20
|
+
latest,
|
|
21
|
+
load_changelog,
|
|
22
|
+
parse_changelog,
|
|
23
|
+
render_section,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ReleaseNotesCommand(Command):
|
|
28
|
+
"""Print release notes from the bundled changelog."""
|
|
29
|
+
|
|
30
|
+
def __init__(self) -> None:
|
|
31
|
+
super().__init__(
|
|
32
|
+
"release-notes",
|
|
33
|
+
"Print release notes from the bundled changelog",
|
|
34
|
+
usage="kd /release-notes [VERSION] [--since X.Y.Z | --all]",
|
|
35
|
+
long_description=(
|
|
36
|
+
"Reads the bundled CHANGELOG.md and prints one or more "
|
|
37
|
+
"release sections. Default returns the latest released "
|
|
38
|
+
"section. Pass a version positionally to pin a specific "
|
|
39
|
+
"release, --since to slice everything strictly newer than "
|
|
40
|
+
"a given version, or --all for the complete file. Output "
|
|
41
|
+
"defaults to markdown; -o json or -o yaml emits a "
|
|
42
|
+
"structured list of {version, date, body} dicts for "
|
|
43
|
+
"programmatic consumers. Source: kd/resources/CHANGELOG.md "
|
|
44
|
+
"in the installed wheel."
|
|
45
|
+
),
|
|
46
|
+
examples=[
|
|
47
|
+
"kd /release-notes",
|
|
48
|
+
"kd /release-notes 2.1.0",
|
|
49
|
+
"kd /release-notes --since 2.0.5",
|
|
50
|
+
"kd /release-notes --all -o json",
|
|
51
|
+
],
|
|
52
|
+
see_also=["/primer show"],
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def _register_options(self) -> None:
|
|
56
|
+
self.register_option(
|
|
57
|
+
OptionDeclaration(
|
|
58
|
+
name="since",
|
|
59
|
+
layer=OptionLayer.COMMAND,
|
|
60
|
+
type=str,
|
|
61
|
+
help_text="Return every version strictly newer than this",
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
self.register_option(
|
|
65
|
+
OptionDeclaration(
|
|
66
|
+
name="all",
|
|
67
|
+
layer=OptionLayer.COMMAND,
|
|
68
|
+
type=bool,
|
|
69
|
+
help_text="Return the complete changelog",
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def execute(
|
|
74
|
+
self, args: list[str], options: ResolvedOptions, client: Any = None
|
|
75
|
+
) -> CommandResult:
|
|
76
|
+
version = args[0] if args else None
|
|
77
|
+
since = options.command.get("since")
|
|
78
|
+
show_all = options.command.get("all", False)
|
|
79
|
+
|
|
80
|
+
chosen = [
|
|
81
|
+
flag
|
|
82
|
+
for flag, val in (("VERSION", version), ("--since", since), ("--all", show_all))
|
|
83
|
+
if val
|
|
84
|
+
]
|
|
85
|
+
if len(chosen) > 1:
|
|
86
|
+
raise ValidationError(
|
|
87
|
+
f"VERSION, --since, and --all are mutually exclusive; got {', '.join(chosen)}"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
sections = parse_changelog(load_changelog())
|
|
91
|
+
|
|
92
|
+
if version:
|
|
93
|
+
section = get_version(sections, version)
|
|
94
|
+
if section is None:
|
|
95
|
+
raise ValidationError(
|
|
96
|
+
f"Version {version} not found in changelog. "
|
|
97
|
+
f"Run 'kd /release-notes --all' to see available versions."
|
|
98
|
+
)
|
|
99
|
+
result = [section]
|
|
100
|
+
elif since:
|
|
101
|
+
try:
|
|
102
|
+
result = filter_since(sections, since)
|
|
103
|
+
except InvalidVersion as exc:
|
|
104
|
+
raise ValidationError(f"Invalid version '{since}': {exc}") from exc
|
|
105
|
+
elif show_all:
|
|
106
|
+
result = sections
|
|
107
|
+
else:
|
|
108
|
+
only = latest(sections)
|
|
109
|
+
if only is None:
|
|
110
|
+
raise ValidationError("Changelog has no released versions.")
|
|
111
|
+
result = [only]
|
|
112
|
+
|
|
113
|
+
fmt = options.output.get("output")
|
|
114
|
+
if fmt in ("json", "yaml"):
|
|
115
|
+
return [{"version": s.version, "date": s.date, "body": s.body} for s in result]
|
|
116
|
+
return "\n\n".join(render_section(s) for s in result)
|