proj-flow 0.8.1__py3-none-any.whl → 0.9.0__py3-none-any.whl
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.
- proj_flow/__init__.py +1 -1
- proj_flow/__main__.py +1 -1
- proj_flow/api/__init__.py +11 -2
- proj_flow/api/arg.py +14 -6
- proj_flow/api/env.py +15 -35
- proj_flow/api/release.py +99 -0
- proj_flow/api/step.py +12 -2
- proj_flow/base/__init__.py +2 -2
- proj_flow/base/inspect.py +15 -44
- proj_flow/base/plugins.py +41 -2
- proj_flow/base/registry.py +105 -0
- proj_flow/cli/__init__.py +55 -0
- proj_flow/cli/argument.py +447 -0
- proj_flow/{flow/cli → cli}/finder.py +1 -1
- proj_flow/ext/__init__.py +6 -0
- proj_flow/ext/github/__init__.py +11 -0
- proj_flow/ext/github/cli.py +118 -0
- proj_flow/ext/github/hosting.py +19 -0
- proj_flow/ext/markdown_changelist.py +14 -0
- proj_flow/ext/python/__init__.py +10 -0
- proj_flow/ext/python/rtdocs.py +238 -0
- proj_flow/ext/python/steps.py +71 -0
- proj_flow/ext/python/version.py +98 -0
- proj_flow/ext/re_structured_changelist.py +14 -0
- proj_flow/flow/__init__.py +3 -3
- proj_flow/flow/configs.py +21 -5
- proj_flow/flow/dependency.py +8 -6
- proj_flow/flow/steps.py +6 -9
- proj_flow/log/__init__.py +10 -2
- proj_flow/log/commit.py +19 -4
- proj_flow/log/error.py +31 -0
- proj_flow/log/hosting/github.py +10 -6
- proj_flow/log/msg.py +23 -0
- proj_flow/log/release.py +112 -21
- proj_flow/log/rich_text/__init__.py +0 -12
- proj_flow/log/rich_text/api.py +10 -4
- proj_flow/minimal/__init__.py +11 -0
- proj_flow/{plugins/commands → minimal}/bootstrap.py +2 -2
- proj_flow/{plugins/commands → minimal}/list.py +12 -10
- proj_flow/{plugins/commands → minimal}/run.py +20 -11
- proj_flow/{plugins/commands → minimal}/system.py +2 -2
- proj_flow/plugins/__init__.py +1 -1
- proj_flow/template/layers/base/.flow/matrix.yml +1 -1
- proj_flow/template/layers/cmake/CMakeLists.txt.mustache +1 -1
- proj_flow/template/layers/github_actions/.github/workflows/build.yml +1 -1
- proj_flow/template/layers/github_social/.github/ISSUE_TEMPLATE/feature_request.md.mustache +1 -1
- {proj_flow-0.8.1.dist-info → proj_flow-0.9.0.dist-info}/METADATA +6 -5
- {proj_flow-0.8.1.dist-info → proj_flow-0.9.0.dist-info}/RECORD +51 -37
- proj_flow-0.9.0.dist-info/entry_points.txt +2 -0
- proj_flow/flow/cli/__init__.py +0 -66
- proj_flow/flow/cli/cmds.py +0 -385
- proj_flow-0.8.1.dist-info/entry_points.txt +0 -2
- {proj_flow-0.8.1.dist-info → proj_flow-0.9.0.dist-info}/WHEEL +0 -0
- {proj_flow-0.8.1.dist-info → proj_flow-0.9.0.dist-info}/licenses/LICENSE +0 -0
proj_flow/__init__.py
CHANGED
proj_flow/__main__.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# This code is licensed under MIT license (see LICENSE for details)
|
|
3
3
|
|
|
4
4
|
"""
|
|
5
|
-
The **proj_flow.__main__** allows *
|
|
5
|
+
The **proj_flow.__main__** allows *Project Flow* to be called as Python component.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from .flow.cli import main
|
proj_flow/api/__init__.py
CHANGED
|
@@ -5,6 +5,15 @@
|
|
|
5
5
|
The **proj_flow.api** contains public APIs, usable in third-party plugins.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from . import arg, completers, ctx, env, init, makefile, step
|
|
8
|
+
from . import arg, completers, ctx, env, init, makefile, release, step
|
|
9
9
|
|
|
10
|
-
__all__ = [
|
|
10
|
+
__all__ = [
|
|
11
|
+
"arg",
|
|
12
|
+
"completers",
|
|
13
|
+
"ctx",
|
|
14
|
+
"env",
|
|
15
|
+
"init",
|
|
16
|
+
"makefile",
|
|
17
|
+
"release",
|
|
18
|
+
"step",
|
|
19
|
+
]
|
proj_flow/api/arg.py
CHANGED
|
@@ -24,7 +24,7 @@ class Argument:
|
|
|
24
24
|
action: typing.Union[str, argparse.Action, None] = None
|
|
25
25
|
default: typing.Optional[typing.Any] = None
|
|
26
26
|
choices: typing.Optional[typing.List[str]] = None
|
|
27
|
-
completer: typing.Optional[
|
|
27
|
+
completer: typing.Optional[_inspect.Function] = None
|
|
28
28
|
|
|
29
29
|
def visit(self, parser: argparse.ArgumentParser, name: str):
|
|
30
30
|
kwargs = {}
|
|
@@ -68,11 +68,16 @@ class FlagArgument(Argument):
|
|
|
68
68
|
@dataclass
|
|
69
69
|
class _Command:
|
|
70
70
|
name: str
|
|
71
|
-
entry: typing.Optional[
|
|
71
|
+
entry: typing.Optional[_inspect.Function]
|
|
72
72
|
doc: typing.Optional[str]
|
|
73
73
|
subs: typing.Dict[str, "_Command"]
|
|
74
74
|
|
|
75
|
-
def add(
|
|
75
|
+
def add(
|
|
76
|
+
self,
|
|
77
|
+
names: typing.List[str],
|
|
78
|
+
entry: _inspect.Function,
|
|
79
|
+
doc: typing.Optional[str],
|
|
80
|
+
):
|
|
76
81
|
name = names[0]
|
|
77
82
|
rest = names[1:]
|
|
78
83
|
if len(rest):
|
|
@@ -97,15 +102,18 @@ _known_commands = _Command("", None, None, {})
|
|
|
97
102
|
_autodoc = {
|
|
98
103
|
"proj_flow.flow.configs.Configs": "Current configuration list.",
|
|
99
104
|
"proj_flow.api.env.Runtime": "Tools and print messages, while respecting ``--dry-run``, ``--silent`` and ``--verbose``.",
|
|
105
|
+
"proj_flow.cli.argument.Command": "The Command object attached to this @command function.",
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
|
|
103
109
|
def command(*name: str):
|
|
104
|
-
def wrap(
|
|
110
|
+
def wrap(function: object):
|
|
111
|
+
entry = typing.cast(_inspect.Function, function)
|
|
105
112
|
global _known_commands
|
|
106
|
-
|
|
113
|
+
orig_doc = inspect.getdoc(entry)
|
|
114
|
+
_known_commands.add(list(name), entry, orig_doc)
|
|
107
115
|
|
|
108
|
-
doc =
|
|
116
|
+
doc = orig_doc or ""
|
|
109
117
|
if doc:
|
|
110
118
|
doc += "\n\n"
|
|
111
119
|
|
proj_flow/api/env.py
CHANGED
|
@@ -23,9 +23,7 @@ from dataclasses import dataclass
|
|
|
23
23
|
from enum import Enum
|
|
24
24
|
from typing import Any, Callable, Dict, List, Optional, Union, cast
|
|
25
25
|
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
from proj_flow.base import uname
|
|
26
|
+
from proj_flow.base import plugins, uname
|
|
29
27
|
from proj_flow.base.plugins import load_module_plugins
|
|
30
28
|
|
|
31
29
|
platform = uname.uname()[0]
|
|
@@ -132,47 +130,29 @@ class FlowConfig:
|
|
|
132
130
|
self.root = cfg.root
|
|
133
131
|
else:
|
|
134
132
|
self.root = os.path.abspath(root)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
encoding="UTF-8",
|
|
139
|
-
) as f:
|
|
140
|
-
self._cfg = yaml.load(f, Loader=yaml.Loader)
|
|
141
|
-
except FileNotFoundError:
|
|
142
|
-
self._cfg = {}
|
|
133
|
+
self._cfg = plugins.load_data(
|
|
134
|
+
os.path.join(self.root, ".flow", "config.json")
|
|
135
|
+
)
|
|
143
136
|
|
|
144
137
|
self._propagate_compilers()
|
|
145
|
-
self.
|
|
138
|
+
self._load_extensions()
|
|
146
139
|
|
|
147
140
|
def _propagate_compilers(self):
|
|
148
141
|
global _flow_config_default_compiler
|
|
149
142
|
_flow_config_default_compiler = self.compiler_os_default
|
|
150
143
|
|
|
151
|
-
def
|
|
152
|
-
|
|
153
|
-
|
|
144
|
+
def _load_extensions(self):
|
|
145
|
+
extensions = cast(List[str], self._cfg.get("extensions", []))
|
|
146
|
+
extensions.insert(0, "proj_flow.minimal")
|
|
154
147
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
plugins = importlib.import_module(local_plugins)
|
|
161
|
-
load_module_plugins(plugins, can_fail=True)
|
|
162
|
-
return
|
|
163
|
-
|
|
164
|
-
sys.path.insert(0, local_plugins)
|
|
165
|
-
|
|
166
|
-
for root, dirnames, _ in os.walk(local_plugins):
|
|
167
|
-
for dirname in dirnames:
|
|
168
|
-
init = os.path.join(root, dirname, "__init__.py")
|
|
169
|
-
if not os.path.isfile(init):
|
|
170
|
-
continue
|
|
171
|
-
plugins = importlib.import_module(dirname)
|
|
172
|
-
load_module_plugins(plugins, can_fail=True)
|
|
173
|
-
dirnames[:] = []
|
|
148
|
+
local_extensions = os.path.abspath(
|
|
149
|
+
os.path.join(self.root, ".flow", "extensions")
|
|
150
|
+
)
|
|
151
|
+
if os.path.isdir(local_extensions):
|
|
152
|
+
sys.path.insert(0, local_extensions)
|
|
174
153
|
|
|
175
|
-
|
|
154
|
+
for extension in extensions:
|
|
155
|
+
importlib.import_module(extension)
|
|
176
156
|
|
|
177
157
|
@property
|
|
178
158
|
def entry(self) -> Dict[str, dict]:
|
proj_flow/api/release.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Copyright (c) 2025 Marcin Zdun
|
|
2
|
+
# This code is licensed under MIT license (see LICENSE for details)
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
The **proj_flow.api.release** provides :class:`ProjectSuite` extension point.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import NamedTuple, Optional, Union
|
|
13
|
+
|
|
14
|
+
from proj_flow.api import env
|
|
15
|
+
from proj_flow.base import registry
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Arg(NamedTuple):
|
|
19
|
+
value: str
|
|
20
|
+
offset: int
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
NO_ARG = Arg("", -1)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Decl(NamedTuple):
|
|
27
|
+
name: str
|
|
28
|
+
value: str
|
|
29
|
+
offset: int
|
|
30
|
+
|
|
31
|
+
def __str__(self):
|
|
32
|
+
return self.value
|
|
33
|
+
|
|
34
|
+
def asArg(self):
|
|
35
|
+
return Arg(self.value, self.offset)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Version(NamedTuple):
|
|
39
|
+
core: Arg
|
|
40
|
+
stability: Arg
|
|
41
|
+
|
|
42
|
+
def __str__(self):
|
|
43
|
+
return f"{self.core.value}{self.stability.value}"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class Project:
|
|
48
|
+
package_root: str
|
|
49
|
+
version: Version
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def archive_name(self):
|
|
53
|
+
return f"{self.package_root}-{self.version}"
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def tag_name(self):
|
|
57
|
+
return f"v{self.version}"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ProjectSuite(ABC):
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def get_project(self, rt: env.Runtime) -> Optional[Project]: ...
|
|
63
|
+
|
|
64
|
+
def set_version(self, rt: env.Runtime, version: str):
|
|
65
|
+
core = re.split(r"([0-9]+\.[0-9]+\.[0-9]+)", version, maxsplit=1)[1]
|
|
66
|
+
stability = version[len(core) :]
|
|
67
|
+
|
|
68
|
+
project = self.get_project(rt)
|
|
69
|
+
if project:
|
|
70
|
+
self.patch_project(rt, project.version.core, core)
|
|
71
|
+
|
|
72
|
+
project = self.get_project(rt)
|
|
73
|
+
if project:
|
|
74
|
+
version_pos = project.version
|
|
75
|
+
|
|
76
|
+
if len(stability):
|
|
77
|
+
self.patch_project(rt, version_pos.stability, stability)
|
|
78
|
+
elif len(version_pos.stability.value):
|
|
79
|
+
self.patch_project(rt, version_pos.stability, "")
|
|
80
|
+
|
|
81
|
+
@abstractmethod
|
|
82
|
+
def get_version_file_path(self, rt: env.Runtime) -> Optional[str]: ...
|
|
83
|
+
|
|
84
|
+
def patch_project(self, rt: env.Runtime, pos: Arg, newValue: str):
|
|
85
|
+
path = self.get_version_file_path(rt)
|
|
86
|
+
|
|
87
|
+
if not path or not os.path.isfile(path):
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
with open(path, "r", encoding="UTF-8") as input:
|
|
91
|
+
text = input.read()
|
|
92
|
+
|
|
93
|
+
patched = text[: pos.offset] + newValue + text[pos.offset + len(pos.value) :]
|
|
94
|
+
|
|
95
|
+
with open(path, "w", encoding="UTF-8") as input:
|
|
96
|
+
input.write(patched)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
project_suites = registry.Registry[ProjectSuite]("ProjectSuite")
|
proj_flow/api/step.py
CHANGED
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
The **proj_flow.api.step** exposes APIs used by run extensions.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import os
|
|
8
9
|
from abc import ABC, abstractmethod
|
|
9
10
|
from typing import List, cast
|
|
10
11
|
|
|
11
12
|
from proj_flow.api.env import Config, Runtime
|
|
13
|
+
from proj_flow.base import inspect as _inspect
|
|
12
14
|
from proj_flow.base import matrix
|
|
13
15
|
|
|
14
16
|
|
|
@@ -77,7 +79,8 @@ def _register_step(step: Step):
|
|
|
77
79
|
|
|
78
80
|
name = step.name
|
|
79
81
|
if name in [step.name for step in __steps]:
|
|
80
|
-
|
|
82
|
+
if "READTHEDOCS" not in os.environ:
|
|
83
|
+
raise NameError(f"Step {name} already registered")
|
|
81
84
|
|
|
82
85
|
__steps.append(step)
|
|
83
86
|
|
|
@@ -115,7 +118,7 @@ def _name_list(label: str, names: List[str], template="`{}`") -> str:
|
|
|
115
118
|
return f"\n:{label}: {prefix}{em[-1]}"
|
|
116
119
|
|
|
117
120
|
|
|
118
|
-
def _make_private(f:
|
|
121
|
+
def _make_private(f: _inspect.Function):
|
|
119
122
|
if f.__doc__:
|
|
120
123
|
f.__doc__ += "\n\n:meta private:\n"
|
|
121
124
|
else:
|
|
@@ -171,3 +174,10 @@ def register(cls=None):
|
|
|
171
174
|
return impl
|
|
172
175
|
|
|
173
176
|
return impl(cls)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def verbose_info():
|
|
180
|
+
for step in __steps:
|
|
181
|
+
print(
|
|
182
|
+
f'-- Step: adding "{step.name}" from `{step.__module__}.{step.__class__.__name__}`'
|
|
183
|
+
)
|
proj_flow/base/__init__.py
CHANGED
|
@@ -6,6 +6,6 @@ The **proj_flow.base** contains low-level tools for higher-level parts of the
|
|
|
6
6
|
library.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from . import cmd, matrix, plugins, uname
|
|
9
|
+
from . import cmd, matrix, plugins, registry, uname
|
|
10
10
|
|
|
11
|
-
__all__ = ["cmd", "matrix", "plugins", "uname"]
|
|
11
|
+
__all__ = ["cmd", "matrix", "plugins", "registry", "uname"]
|
proj_flow/base/inspect.py
CHANGED
|
@@ -11,6 +11,14 @@ import typing
|
|
|
11
11
|
from dataclasses import dataclass
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
class Function(typing.Protocol):
|
|
15
|
+
"""Replacement for :py:func:`callable` as universal argument type."""
|
|
16
|
+
|
|
17
|
+
__name__: str
|
|
18
|
+
|
|
19
|
+
def __call__(self, **kwarg) -> typing.Any: ...
|
|
20
|
+
|
|
21
|
+
|
|
14
22
|
@dataclass
|
|
15
23
|
class Argument:
|
|
16
24
|
"""Extracted argument type"""
|
|
@@ -27,7 +35,7 @@ class Argument:
|
|
|
27
35
|
metadata: typing.List[typing.Any]
|
|
28
36
|
|
|
29
37
|
|
|
30
|
-
def signature(call:
|
|
38
|
+
def signature(call: Function) -> typing.Generator[Argument, None, None]:
|
|
31
39
|
"""
|
|
32
40
|
Extract the arguments from the function and produces list of
|
|
33
41
|
:class:`Argument` objects.
|
|
@@ -56,29 +64,6 @@ def signature(call: callable) -> typing.Generator[Argument, None, None]: # type
|
|
|
56
64
|
yield Argument(name=param_name, type=origin, metadata=metadata)
|
|
57
65
|
|
|
58
66
|
|
|
59
|
-
def _union_args(t):
|
|
60
|
-
origin = typing.get_origin(t)
|
|
61
|
-
|
|
62
|
-
if origin is typing.Union:
|
|
63
|
-
args = typing.get_args(t)
|
|
64
|
-
for arg in args:
|
|
65
|
-
for candidate in _union_args(arg):
|
|
66
|
-
yield candidate
|
|
67
|
-
return
|
|
68
|
-
|
|
69
|
-
yield t
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def _unique_union_arg_names(t):
|
|
73
|
-
seen: typing.Set[str] = set()
|
|
74
|
-
for arg in _union_args(t):
|
|
75
|
-
name = type_name(arg)
|
|
76
|
-
if name in seen:
|
|
77
|
-
continue
|
|
78
|
-
seen.add(name)
|
|
79
|
-
yield name
|
|
80
|
-
|
|
81
|
-
|
|
82
67
|
def type_name(t: type) -> str:
|
|
83
68
|
"""
|
|
84
69
|
Converts a type to simplified string representing that type.
|
|
@@ -96,7 +81,12 @@ def type_name(t: type) -> str:
|
|
|
96
81
|
origin = typing.get_origin(t)
|
|
97
82
|
|
|
98
83
|
if origin is typing.Union:
|
|
99
|
-
|
|
84
|
+
args = typing.get_args(t)
|
|
85
|
+
names: typing.List[str] = []
|
|
86
|
+
for arg in args:
|
|
87
|
+
name = type_name(arg)
|
|
88
|
+
names.append(name)
|
|
89
|
+
return " | ".join(names)
|
|
100
90
|
|
|
101
91
|
if origin is None:
|
|
102
92
|
return "?"
|
|
@@ -112,22 +102,3 @@ def type_name(t: type) -> str:
|
|
|
112
102
|
return f"{origin.__name__}[{arg_list}]"
|
|
113
103
|
|
|
114
104
|
return typing.cast(str, origin.__name__)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def union_arg(t: type) -> typing.Generator[typing.Any, None, None]:
|
|
118
|
-
"""
|
|
119
|
-
Extract the arguments from :py:class:`typing.Union`. In case some of the
|
|
120
|
-
generic argument are unions themsleves, this function will flatten
|
|
121
|
-
the resulting list of types.
|
|
122
|
-
|
|
123
|
-
:param t: Type to ananlyze.
|
|
124
|
-
:returns: List of the argument, if the argument is a Union.
|
|
125
|
-
"""
|
|
126
|
-
|
|
127
|
-
seen: typing.Set[str] = set()
|
|
128
|
-
for arg in _union_args(t):
|
|
129
|
-
name = type_name(arg)
|
|
130
|
-
if name in seen:
|
|
131
|
-
continue
|
|
132
|
-
seen.add(name)
|
|
133
|
-
yield arg
|
proj_flow/base/plugins.py
CHANGED
|
@@ -6,9 +6,48 @@ The **proj_flow.base.plugins** provide the plugin enumeration helpers.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import importlib
|
|
9
|
+
import json
|
|
9
10
|
import os
|
|
10
11
|
from types import ModuleType
|
|
11
|
-
from typing import Optional
|
|
12
|
+
from typing import Optional, cast
|
|
13
|
+
|
|
14
|
+
import yaml
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_yaml(filename: str):
|
|
18
|
+
with open(filename) as src:
|
|
19
|
+
return cast(dict, yaml.load(src, Loader=yaml.Loader))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def load_json(filename: str):
|
|
23
|
+
with open(filename) as src:
|
|
24
|
+
return cast(dict, json.load(src))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
LOADERS = {
|
|
28
|
+
".json": load_json,
|
|
29
|
+
".yml": load_yaml,
|
|
30
|
+
".yaml": load_yaml,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def load_data(filename: str):
|
|
35
|
+
prefix, ext = os.path.splitext(filename)
|
|
36
|
+
loader = LOADERS.get(ext.lower())
|
|
37
|
+
if loader:
|
|
38
|
+
try:
|
|
39
|
+
return loader(filename)
|
|
40
|
+
except Exception:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
for new_ext, loader in LOADERS.items():
|
|
44
|
+
new_filename = prefix + new_ext
|
|
45
|
+
try:
|
|
46
|
+
return loader(new_filename)
|
|
47
|
+
except Exception:
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
return {}
|
|
12
51
|
|
|
13
52
|
|
|
14
53
|
def _load_plugins(directory: str, package: Optional[str], can_fail=False):
|
|
@@ -40,5 +79,5 @@ def load_module_plugins(mod: ModuleType, can_fail=False):
|
|
|
40
79
|
spec = mod.__spec__
|
|
41
80
|
if not spec:
|
|
42
81
|
return
|
|
43
|
-
for location in spec.submodule_search_locations:
|
|
82
|
+
for location in spec.submodule_search_locations: # type: ignore
|
|
44
83
|
_load_plugins(location, spec.name, can_fail)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Copyright (c) 2024 Marcin Zdun
|
|
2
|
+
# This code is licensed under MIT license (see LICENSE for details)
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
The **proj_flow.base.registry**
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import typing
|
|
9
|
+
|
|
10
|
+
T = typing.TypeVar("T")
|
|
11
|
+
K = typing.TypeVar("K")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Registry(typing.Generic[T]):
|
|
15
|
+
"""
|
|
16
|
+
Provides simple registry with the decorator attached to it, which
|
|
17
|
+
implements extension point for plugin system. An extension point is a
|
|
18
|
+
value created from this generic, with extendable interface type as
|
|
19
|
+
the generic argument.
|
|
20
|
+
|
|
21
|
+
Known decorators
|
|
22
|
+
................
|
|
23
|
+
|
|
24
|
+
:data:`proj_flow.api.release.project_suites`
|
|
25
|
+
:Argument: :class:`ProjectSuite <proj_flow.api.release.ProjectSuite>`
|
|
26
|
+
:Used by: :func:`src.proj_flow.log.release.add_release`
|
|
27
|
+
|
|
28
|
+
Project version reader and updater, package file name builder.
|
|
29
|
+
|
|
30
|
+
:data:`proj_flow.log.release.version_updaters`
|
|
31
|
+
:Argument: :class:`VersionUpdaters <proj_flow.api.release.VersionUpdaters>`
|
|
32
|
+
:Used by: :func:`src.proj_flow.log.release.add_release`
|
|
33
|
+
|
|
34
|
+
Additional version updaters, for instance, path to schema reference on
|
|
35
|
+
GitHub.
|
|
36
|
+
|
|
37
|
+
:data:`proj_flow.log.rich_text.api.changelog_generators`
|
|
38
|
+
:Argument: :class:`ChangelogGenerator <proj_flow.log.rich_text.api.ChangelogGenerator>`
|
|
39
|
+
:Used by: :func:`src.proj_flow.ext.github.cli.release`
|
|
40
|
+
|
|
41
|
+
Changelog note generator used in CHANGELOG file. Not to confuse with
|
|
42
|
+
generator, which may be used internally by Hosting.add_release.
|
|
43
|
+
|
|
44
|
+
Example
|
|
45
|
+
.......
|
|
46
|
+
|
|
47
|
+
.. code-block:: python
|
|
48
|
+
|
|
49
|
+
class Animal(ABC)
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def speak(self): ...
|
|
52
|
+
|
|
53
|
+
animals = Registry[Animal]("Animal")
|
|
54
|
+
|
|
55
|
+
def speak_all():
|
|
56
|
+
for animal in animals.get():
|
|
57
|
+
animal.speak()
|
|
58
|
+
|
|
59
|
+
@animals.add
|
|
60
|
+
class Dog(Animal):
|
|
61
|
+
def speak(self):
|
|
62
|
+
print("woof!")
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
name: str
|
|
66
|
+
container: typing.List[T]
|
|
67
|
+
|
|
68
|
+
def __init__(self, name: str):
|
|
69
|
+
self.name = name
|
|
70
|
+
self.container = []
|
|
71
|
+
_debug_copies.append(self)
|
|
72
|
+
|
|
73
|
+
def add(self, cls: typing.Type[T]):
|
|
74
|
+
obj: T = cls()
|
|
75
|
+
self.container.append(obj)
|
|
76
|
+
return cls
|
|
77
|
+
|
|
78
|
+
def get(self):
|
|
79
|
+
return self.container
|
|
80
|
+
|
|
81
|
+
def find(
|
|
82
|
+
self, filter: typing.Callable[[T], K]
|
|
83
|
+
) -> typing.Tuple[typing.Optional[T], typing.Optional[K]]:
|
|
84
|
+
for item in self.container:
|
|
85
|
+
candidate = filter(item)
|
|
86
|
+
if candidate is not None:
|
|
87
|
+
return item, candidate
|
|
88
|
+
return None, None
|
|
89
|
+
|
|
90
|
+
def first(self) -> typing.Optional[T]:
|
|
91
|
+
try:
|
|
92
|
+
return next(self.container.__iter__())
|
|
93
|
+
except StopIteration:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
_debug_copies: typing.List[Registry] = []
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def verbose_info():
|
|
101
|
+
for registry in _debug_copies:
|
|
102
|
+
for item in registry.container:
|
|
103
|
+
print(
|
|
104
|
+
f"-- {registry.name}: adding `{item.__module__}.{item.__class__.__name__}`"
|
|
105
|
+
)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# PYTHON_ARGCOMPLETE_OK
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2025 Marcin Zdun
|
|
4
|
+
# This code is licensed under MIT license (see LICENSE for details)
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
The **proj_flow.cli** provides command-line entry for the *Project Flow*.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
from pprint import pprint
|
|
14
|
+
from typing import Dict, Optional, Tuple
|
|
15
|
+
|
|
16
|
+
from proj_flow.api import arg, env
|
|
17
|
+
from proj_flow.cli import argument, finder
|
|
18
|
+
from proj_flow.flow import steps
|
|
19
|
+
|
|
20
|
+
__all__ = ["argument", "finder", "main"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main():
|
|
24
|
+
"""Entry point for ``proj-flow`` tool."""
|
|
25
|
+
try:
|
|
26
|
+
__main()
|
|
27
|
+
except KeyboardInterrupt:
|
|
28
|
+
sys.exit(1)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _change_dir():
|
|
32
|
+
root = argparse.ArgumentParser(
|
|
33
|
+
prog="proj-flow",
|
|
34
|
+
usage="proj-flow [-h] [--version] [-C [dir]] {command} ...",
|
|
35
|
+
add_help=False,
|
|
36
|
+
)
|
|
37
|
+
root.add_argument("-C", dest="cd", nargs="?")
|
|
38
|
+
|
|
39
|
+
args, _ = root.parse_known_args()
|
|
40
|
+
if args.cd:
|
|
41
|
+
os.chdir(args.cd)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def __main():
|
|
45
|
+
_change_dir()
|
|
46
|
+
|
|
47
|
+
flow_cfg = env.FlowConfig(root=finder.autocomplete.find_project())
|
|
48
|
+
steps.clean_aliases(flow_cfg)
|
|
49
|
+
|
|
50
|
+
parser = argument.build_argparser(flow_cfg)
|
|
51
|
+
finder.autocomplete(parser)
|
|
52
|
+
args = parser.parse_args()
|
|
53
|
+
argument.expand_shortcuts(parser, args)
|
|
54
|
+
|
|
55
|
+
raise SystemExit(parser.find_and_run_command(args))
|