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.
Files changed (54) hide show
  1. proj_flow/__init__.py +1 -1
  2. proj_flow/__main__.py +1 -1
  3. proj_flow/api/__init__.py +11 -2
  4. proj_flow/api/arg.py +14 -6
  5. proj_flow/api/env.py +15 -35
  6. proj_flow/api/release.py +99 -0
  7. proj_flow/api/step.py +12 -2
  8. proj_flow/base/__init__.py +2 -2
  9. proj_flow/base/inspect.py +15 -44
  10. proj_flow/base/plugins.py +41 -2
  11. proj_flow/base/registry.py +105 -0
  12. proj_flow/cli/__init__.py +55 -0
  13. proj_flow/cli/argument.py +447 -0
  14. proj_flow/{flow/cli → cli}/finder.py +1 -1
  15. proj_flow/ext/__init__.py +6 -0
  16. proj_flow/ext/github/__init__.py +11 -0
  17. proj_flow/ext/github/cli.py +118 -0
  18. proj_flow/ext/github/hosting.py +19 -0
  19. proj_flow/ext/markdown_changelist.py +14 -0
  20. proj_flow/ext/python/__init__.py +10 -0
  21. proj_flow/ext/python/rtdocs.py +238 -0
  22. proj_flow/ext/python/steps.py +71 -0
  23. proj_flow/ext/python/version.py +98 -0
  24. proj_flow/ext/re_structured_changelist.py +14 -0
  25. proj_flow/flow/__init__.py +3 -3
  26. proj_flow/flow/configs.py +21 -5
  27. proj_flow/flow/dependency.py +8 -6
  28. proj_flow/flow/steps.py +6 -9
  29. proj_flow/log/__init__.py +10 -2
  30. proj_flow/log/commit.py +19 -4
  31. proj_flow/log/error.py +31 -0
  32. proj_flow/log/hosting/github.py +10 -6
  33. proj_flow/log/msg.py +23 -0
  34. proj_flow/log/release.py +112 -21
  35. proj_flow/log/rich_text/__init__.py +0 -12
  36. proj_flow/log/rich_text/api.py +10 -4
  37. proj_flow/minimal/__init__.py +11 -0
  38. proj_flow/{plugins/commands → minimal}/bootstrap.py +2 -2
  39. proj_flow/{plugins/commands → minimal}/list.py +12 -10
  40. proj_flow/{plugins/commands → minimal}/run.py +20 -11
  41. proj_flow/{plugins/commands → minimal}/system.py +2 -2
  42. proj_flow/plugins/__init__.py +1 -1
  43. proj_flow/template/layers/base/.flow/matrix.yml +1 -1
  44. proj_flow/template/layers/cmake/CMakeLists.txt.mustache +1 -1
  45. proj_flow/template/layers/github_actions/.github/workflows/build.yml +1 -1
  46. proj_flow/template/layers/github_social/.github/ISSUE_TEMPLATE/feature_request.md.mustache +1 -1
  47. {proj_flow-0.8.1.dist-info → proj_flow-0.9.0.dist-info}/METADATA +6 -5
  48. {proj_flow-0.8.1.dist-info → proj_flow-0.9.0.dist-info}/RECORD +51 -37
  49. proj_flow-0.9.0.dist-info/entry_points.txt +2 -0
  50. proj_flow/flow/cli/__init__.py +0 -66
  51. proj_flow/flow/cli/cmds.py +0 -385
  52. proj_flow-0.8.1.dist-info/entry_points.txt +0 -2
  53. {proj_flow-0.8.1.dist-info → proj_flow-0.9.0.dist-info}/WHEEL +0 -0
  54. {proj_flow-0.8.1.dist-info → proj_flow-0.9.0.dist-info}/licenses/LICENSE +0 -0
proj_flow/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # Copyright (c) 2025 Marcin Zdun
2
2
  # This code is licensed under MIT license (see LICENSE for details)
3
3
 
4
- __version__ = "0.8.1"
4
+ __version__ = "0.9.0"
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 *C++ flow* to be called as Python component.
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__ = ["arg", "completers", "ctx", "env", "init", "makefile", "step"]
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[callable] = None # type: ignore
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[callable] # type: ignore
71
+ entry: typing.Optional[_inspect.Function]
72
72
  doc: typing.Optional[str]
73
73
  subs: typing.Dict[str, "_Command"]
74
74
 
75
- def add(self, names: typing.List[str], entry: callable, doc: typing.Optional[str]): # type: ignore
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(entry: callable): # type: ignore
110
+ def wrap(function: object):
111
+ entry = typing.cast(_inspect.Function, function)
105
112
  global _known_commands
106
- _known_commands.add(list(name), entry, entry.__doc__)
113
+ orig_doc = inspect.getdoc(entry)
114
+ _known_commands.add(list(name), entry, orig_doc)
107
115
 
108
- doc = inspect.getdoc(entry) or ""
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 yaml
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
- try:
136
- with open(
137
- os.path.join(self.root, ".flow", "config.yml"),
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._load_plugins()
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 _load_plugins(self):
152
- std_plugins = importlib.import_module("proj_flow.plugins")
153
- load_module_plugins(std_plugins)
144
+ def _load_extensions(self):
145
+ extensions = cast(List[str], self._cfg.get("extensions", []))
146
+ extensions.insert(0, "proj_flow.minimal")
154
147
 
155
- local_plugins = os.path.abspath(os.path.join(self.root, ".flow", "extensions"))
156
- if not os.path.exists(local_plugins):
157
- return
158
-
159
- if not os.path.isdir(local_plugins):
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
- sys.path.pop(0)
154
+ for extension in extensions:
155
+ importlib.import_module(extension)
176
156
 
177
157
  @property
178
158
  def entry(self) -> Dict[str, dict]:
@@ -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
- raise NameError(f"Step {name} already registered")
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: callable):
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
+ )
@@ -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: callable) -> typing.Generator[Argument, None, None]: # type: ignore
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
- return "|".join(_unique_union_arg_names(t))
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))