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
@@ -11,7 +11,7 @@ from typing import Any, List, NamedTuple, Optional, Tuple, cast
11
11
 
12
12
  from proj_flow.api import env
13
13
  from proj_flow.base import cmd
14
- from proj_flow.log import commit, release
14
+ from proj_flow.log import commit, msg
15
15
 
16
16
 
17
17
  class _GitHub(NamedTuple):
@@ -122,21 +122,25 @@ class GitHub(commit.Hosting):
122
122
 
123
123
  return json.loads(proc.stdout)
124
124
 
125
- def draft_a_release(
126
- self, log: commit.ChangeLog, setup: commit.LogSetup, git: commit.Git
125
+ def add_release(
126
+ self,
127
+ log: commit.ChangeLog,
128
+ setup: commit.LogSetup,
129
+ git: commit.Git,
130
+ draft: bool,
127
131
  ) -> commit.ReleaseInfo:
128
132
  if setup.curr_tag is None:
129
133
  git.rt.fatal(f"New tag is needed.")
130
134
 
131
135
  git.push_with_refs(self.remote, "main")
132
136
 
133
- body = release.ReleaseMessage(setup).format_changelog(log)
137
+ body = msg.ReleaseMessage(setup).format_changelog(log)
134
138
 
135
139
  rel = {
136
140
  "tag_name": setup.curr_tag,
137
141
  "name": setup.curr_tag,
138
142
  "body": body,
139
- "draft": True,
143
+ "draft": draft,
140
144
  "prerelease": len(setup.curr_tag.split("-", 1)) > 1,
141
145
  }
142
146
 
@@ -157,7 +161,7 @@ class GitHub(commit.Hosting):
157
161
  def from_repo(git: commit.Git, remote: Optional[str] = None):
158
162
  info = _find_github(git, remote)
159
163
  if info == _NO_GITHUB:
160
- return commit.NoHosting()
164
+ return None
161
165
  return GitHub(info, git.rt)
162
166
 
163
167
 
proj_flow/log/msg.py CHANGED
@@ -185,6 +185,29 @@ class CommitMessage(ChangelogMessage):
185
185
  return result
186
186
 
187
187
 
188
+ class ReleaseMessage(ChangelogMessage):
189
+ setup: commit.LogSetup
190
+
191
+ def __init__(self, setup: commit.LogSetup):
192
+ self.setup = setup
193
+
194
+ def scope_text(self, scope: str):
195
+ if len(scope):
196
+ scope = f"**{scope}**: "
197
+ return scope
198
+
199
+ def short_hash_link(self, link: commit.Link):
200
+ url = self.setup.single_commit_link(link)
201
+ if not url:
202
+ return link.short_hash
203
+ return f"[{link.short_hash}]({url})"
204
+
205
+ def outro_lines(self, lines: List[str]) -> None:
206
+ url = self.setup.commit_listing_link()
207
+ if url:
208
+ lines.append(f"**Full Changelog**: {url}")
209
+
210
+
188
211
  def _find_breaking_notes(links: List[commit.Link]) -> List[str]:
189
212
  breaking: List[str] = []
190
213
  for link in links:
proj_flow/log/release.py CHANGED
@@ -2,33 +2,124 @@
2
2
  # This code is licensed under MIT license (see LICENSE for details)
3
3
 
4
4
  """
5
- The **proj_flow.log.release** builds a changelog message for hosting
6
- service release.
5
+ The **proj_flow.log.release** performs a relase on the hosting service.
7
6
  """
8
7
 
9
- from typing import List
8
+ import typing
9
+ from abc import ABC, abstractmethod
10
+ from typing import Iterable, Union
10
11
 
11
- from proj_flow.log import commit, msg
12
+ from proj_flow import api
13
+ from proj_flow.api import env
14
+ from proj_flow.base import registry
15
+ from proj_flow.log import commit, fmt, rich_text
16
+ from proj_flow.log.error import NoProjectError, VersionNotAdvancing
12
17
 
18
+ OneOrMoreStrings = Union[str, Iterable[str]]
13
19
 
14
- class ReleaseMessage(msg.ChangelogMessage):
15
- setup: commit.LogSetup
16
20
 
17
- def __init__(self, setup: commit.LogSetup):
18
- self.setup = setup
21
+ class VersionUpdater(ABC):
22
+ @abstractmethod
23
+ def on_version_change(self, new_version: str) -> OneOrMoreStrings: ...
19
24
 
20
- def scope_text(self, scope: str):
21
- if len(scope):
22
- scope = f"**{scope}**: "
23
- return scope
24
25
 
25
- def short_hash_link(self, link: commit.Link):
26
- url = self.setup.single_commit_link(link)
27
- if not url:
28
- return link.short_hash
29
- return f"[{link.short_hash}]({url})"
26
+ version_updaters = registry.Registry[VersionUpdater]("VersionUpdater")
30
27
 
31
- def outro_lines(self, lines: List[str]) -> None:
32
- url = self.setup.commit_listing_link()
33
- if url:
34
- lines.append(f"**Full Changelog**: {url}")
28
+
29
+ def _bump_version(ver: str, level: commit.Level):
30
+ split = ver.split("-", 1)
31
+ if len(split) == 2:
32
+ stability = f"-{split[1]}"
33
+ else:
34
+ stability = ""
35
+
36
+ semver = [int(s) for s in split[0].split(".")]
37
+ while len(semver) < 3:
38
+ semver.append(0)
39
+ semver = semver[:3]
40
+
41
+ if level.value > commit.Level.BENIGN.value:
42
+ # This turns [1, 2, 3] through 4 - x into [3, 2, 1]
43
+ lvl = commit.Level.BREAKING.value - level.value
44
+ semver[lvl] += 1
45
+ for index in range(lvl + 1, len(semver)):
46
+ semver[index] = 0
47
+
48
+ return ".".join(str(v) for v in semver) + stability
49
+
50
+
51
+ def _get_project(rt: env.Runtime):
52
+ def wrap(suite: api.release.ProjectSuite):
53
+ return suite.get_project(rt)
54
+
55
+ return wrap
56
+
57
+
58
+ def add_release(
59
+ rt: env.Runtime,
60
+ forced_level: typing.Optional[commit.Level],
61
+ take_all: bool,
62
+ draft: bool,
63
+ generator: rich_text.api.ChangelogGenerator,
64
+ git: commit.Git,
65
+ hosting: commit.Hosting,
66
+ ):
67
+ """
68
+ Bumps the project version based on current git logs, creates a "chore"
69
+ commit for the change, attaches an annotated tag with the version number
70
+ and pushes it all to hosting.
71
+ """
72
+
73
+ suite, project = api.release.project_suites.find(_get_project(rt))
74
+
75
+ if not project or not suite:
76
+ raise NoProjectError()
77
+
78
+ tags = git.tag_list(silent=True)
79
+
80
+ prev_tag = tags[-1] if len(tags) > 0 else None
81
+
82
+ setup = commit.LogSetup(hosting, prev_tag, None, take_all=take_all)
83
+ changelog, log_level = git.get_log(setup)
84
+
85
+ project_version = f"{project.version}"
86
+ next_version = _bump_version(project_version, forced_level or log_level)
87
+ setup.curr_tag = f"v{next_version}"
88
+
89
+ if next_version == project_version:
90
+ raise VersionNotAdvancing(next_version)
91
+
92
+ if setup.curr_tag in tags:
93
+ rt.fatal(f"Tag {setup.curr_tag} already exists.")
94
+
95
+ files_to_commit: typing.List[str] = []
96
+ if not rt.dry_run:
97
+ generator.update_changelog(changelog, setup, rt)
98
+ files_to_commit.append(generator.filename)
99
+
100
+ suite.set_version(rt, next_version)
101
+ version_path = suite.get_version_file_path(rt)
102
+ if version_path:
103
+ files_to_commit.append(version_path)
104
+
105
+ for updater in version_updaters.get():
106
+ modified = updater.on_version_change(next_version)
107
+ if isinstance(modified, str):
108
+ files_to_commit.append(modified)
109
+ else:
110
+ files_to_commit.extend(modified)
111
+ else:
112
+ files_to_commit.append(generator.filename)
113
+ version_path = suite.get_version_file_path(rt)
114
+ if version_path:
115
+ files_to_commit.append(version_path)
116
+
117
+ commit_message = f"release {next_version}"
118
+ git.add_files(*files_to_commit)
119
+ git.commit(f"chore: {commit_message}{fmt.format_commit_message(changelog)}")
120
+ git.annotated_tag(setup.curr_tag, commit_message)
121
+
122
+ if hosting.is_active:
123
+ draft_url = hosting.add_release(changelog, setup, git, draft).draft_url
124
+ if draft_url:
125
+ rt.message("-- Visit draft at", draft_url, level=env.Msg.ALWAYS)
@@ -8,15 +8,3 @@ The **proj_flow.log.rich_text** allows to operate on CHANGELOG.md.
8
8
  from . import api, markdown, re_structured_text
9
9
 
10
10
  __all__ = ["api", "markdown", "re_structured_text"]
11
-
12
-
13
- def select_generator(rst: bool = False) -> api.ChangelogGenerator:
14
- """
15
- Selects proper generator/formatter for Changelog messages.
16
-
17
- :param rst: Generator selector. When true, returns reStructuredText
18
- generator, Markdown otherwise.
19
- """
20
- if rst:
21
- return re_structured_text.ChangelogGenerator()
22
- return markdown.ChangelogGenerator()
@@ -11,11 +11,12 @@ import os
11
11
  import re
12
12
  from typing import Dict, List, Type
13
13
 
14
+ from proj_flow import base
14
15
  from proj_flow.api import env
15
- from proj_flow.log import commit, release
16
+ from proj_flow.log import commit, msg
16
17
 
17
18
 
18
- class FileUpdate(release.ReleaseMessage):
19
+ class FileUpdate(msg.ReleaseMessage):
19
20
  commit_date: str
20
21
 
21
22
  def __init__(self, setup: commit.LogSetup, commit_date: str):
@@ -24,6 +25,10 @@ class FileUpdate(release.ReleaseMessage):
24
25
 
25
26
 
26
27
  class ChangelogGenerator(abc.ABC):
28
+ @property
29
+ def filename(self) -> str:
30
+ return f"CHANGELOG{self.ext}"
31
+
27
32
  @property
28
33
  @abc.abstractmethod
29
34
  def formatter(self) -> Type[FileUpdate]: ...
@@ -101,7 +106,7 @@ class ChangelogGenerator(abc.ABC):
101
106
  setup, commit.read_tag_date(setup.curr_tag or "HEAD", rt)
102
107
  )
103
108
  text = formatter.format_changelog(log)
104
- filename = f"CHANGELOG{self.ext}"
109
+ filename = self.filename
105
110
  path = os.path.join(rt.root, filename)
106
111
 
107
112
  try:
@@ -123,4 +128,5 @@ class ChangelogGenerator(abc.ABC):
123
128
  with open(filename, "wb") as f:
124
129
  f.write(new_text.encode("UTF-8"))
125
130
 
126
- return [filename]
131
+
132
+ changelog_generators = base.registry.Registry[ChangelogGenerator]("ChangelogGenerator")
@@ -0,0 +1,11 @@
1
+ # Copyright (c) 2025 Marcin Zdun
2
+ # This code is licensed under MIT license (see LICENSE for details)
3
+
4
+ """
5
+ The **proj_flow.minimal** defines minimal extension package: ``bootstrap``
6
+ and ``run`` commands, with basic set of steps.
7
+ """
8
+
9
+ from . import bootstrap, list, run, system
10
+
11
+ __all__ = ["bootstrap", "list", "run", "system"]
@@ -2,12 +2,12 @@
2
2
  # This code is licensed under MIT license (see LICENSE for details)
3
3
 
4
4
  """
5
- The **proj_flow.plugins.commands.bootstrap** implements ``./flow bootstrap`` command.
5
+ The **proj_flow.minimal.bootstrap** implements ``./flow bootstrap`` command.
6
6
  """
7
7
 
8
8
  import os
9
9
 
10
- from proj_flow.api import arg, env
10
+ from proj_flow.api import arg
11
11
 
12
12
 
13
13
  @arg.command("bootstrap")
@@ -2,13 +2,13 @@
2
2
  # This code is licensed under MIT license (see LICENSE for details)
3
3
 
4
4
  """
5
- The **proj_flow.plugins.commands.list** implements ``./flow list`` command.
5
+ The **proj_flow.minimal.list** implements ``./flow list`` command.
6
6
  """
7
7
 
8
8
  import os
9
- from typing import Annotated, Dict, List, Optional, Set, cast
9
+ from typing import Annotated, Dict, List, Set, cast
10
10
 
11
- from proj_flow import flow
11
+ from proj_flow import cli
12
12
  from proj_flow.api import arg, env, step
13
13
  from proj_flow.base import matrix
14
14
 
@@ -24,6 +24,7 @@ def main(
24
24
  ],
25
25
  pipe: Annotated[bool, arg.FlagArgument(help="Do not show additional information")],
26
26
  rt: env.Runtime,
27
+ menu: cli.argument.Command,
27
28
  ):
28
29
  """List all the commands and/or steps for proj-flow"""
29
30
 
@@ -38,9 +39,10 @@ def main(
38
39
  configs = True
39
40
 
40
41
  if builtin:
41
- builtin_entries = list(
42
- sorted((entry.name, entry.doc) for entry in flow.cli.cmds.command_list)
43
- )
42
+ root = menu
43
+ while root.parent is not None:
44
+ root = root.parent
45
+ builtin_entries = list(sorted((cmd.name, cmd.doc) for cmd in root.children))
44
46
  if not pipe and len(builtin_entries) > 0:
45
47
  print("Builtin commands")
46
48
  print("----------------")
@@ -68,13 +70,13 @@ def main(
68
70
  print("Known aliases")
69
71
  print("-------------")
70
72
 
71
- for alias in aliases:
73
+ for run_alias in aliases:
72
74
  if pipe:
73
- print(alias.name)
75
+ print(run_alias.name)
74
76
  continue
75
77
 
76
- name = f"{bold}{alias.name}{reset}"
77
- print(f"- {name}: {', '.join(alias.steps)}")
78
+ name = f"{bold}{run_alias.name}{reset}"
79
+ print(f"- {name}: {', '.join(run_alias.steps)}")
78
80
 
79
81
  printed_something = True
80
82
 
@@ -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.plugins.commands.run** implements ``./flow run`` command.
5
+ The **proj_flow.minimal.run** implements ``./flow run`` command.
6
6
  """
7
7
 
8
8
  import os
@@ -19,6 +19,8 @@ from proj_flow.flow.configs import Configs
19
19
 
20
20
  @api.arg.command("run")
21
21
  def main(
22
+ configs: Configs,
23
+ rt: api.env.Runtime,
22
24
  cli_steps: Annotated[
23
25
  Optional[List[str]],
24
26
  api.arg.Argument(
@@ -31,8 +33,6 @@ def main(
31
33
  completer=api.completers.step_completer,
32
34
  ),
33
35
  ],
34
- configs: Configs,
35
- rt: api.env.Runtime,
36
36
  ):
37
37
  """Run automation steps for current project"""
38
38
 
@@ -117,6 +117,7 @@ def run_steps(
117
117
  configs: Configs, rt: api.env.Runtime, program: List[api.step.Step], printed: bool
118
118
  ) -> int:
119
119
  config_count = len(configs.usable)
120
+ steps_ran = 0
120
121
  for config_index in range(config_count):
121
122
  config = configs.usable[config_index]
122
123
  steps = [step for step in program if step.is_active(config, rt)]
@@ -128,20 +129,28 @@ def run_steps(
128
129
  print(file=sys.stderr)
129
130
  printed = True
130
131
 
131
- if config_count < 2:
132
- print(f"- {config.build_name}", file=sys.stderr)
133
- else:
134
- print(
135
- f"- {config_index + 1}/{config_count}: {config.build_name}",
136
- file=sys.stderr,
137
- )
132
+ build_name: Optional[str] = getattr(config, "build_name")
133
+
134
+ if build_name:
135
+ if config_count < 2:
136
+ print(f"- {build_name}", file=sys.stderr)
137
+ else:
138
+ print(
139
+ f"- {config_index + 1}/{config_count}: {build_name}",
140
+ file=sys.stderr,
141
+ )
138
142
 
139
- with compilers_env_setup(config.compiler, rt):
143
+ compilers: List[str] = getattr(config, "compiler", [])
144
+ with compilers_env_setup(compilers, rt):
140
145
  for index in range(step_count):
141
146
  step = steps[index]
142
147
  print(f"-- step {index + 1}/{step_count}: {step.name}", file=sys.stderr)
148
+ steps_ran += 1
143
149
  ret = step.run(config, rt)
144
150
  if ret:
145
151
  return 1
146
152
 
153
+ if steps_ran == 0:
154
+ print("Nothing to do.")
155
+
147
156
  return 0
@@ -2,14 +2,14 @@
2
2
  # This code is licensed under MIT license (see LICENSE for details)
3
3
 
4
4
  """
5
- The **proj_flow.plugins.commands.system** implements ``./flow system`` command.
5
+ The **proj_flow.minimal.system** implements ``./flow system`` command.
6
6
  """
7
7
 
8
8
  import platform
9
9
  import sys
10
10
  from typing import Annotated
11
11
 
12
- from proj_flow.api import arg, env
12
+ from proj_flow.api import arg
13
13
  from proj_flow.base import uname
14
14
 
15
15
 
@@ -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.plugins** provides the runtime comnponents of the *C++ flow*:
5
+ The **proj_flow.plugins** provides the runtime comnponents of the *Project Flow*:
6
6
  the :class:`initialization helpers <proj_flow.api.init.InitStep>` and
7
7
  :class:`run steps <proj_flow.api.step.Step>`.
8
8
  """
@@ -53,7 +53,7 @@ include:
53
53
  - os: ubuntu
54
54
  build_type: Release
55
55
  sanitizer: false
56
-
56
+
57
57
  - os: windows
58
58
  build_type: Release
59
59
  conan_settings: [ compiler.runtime=MD ]
@@ -135,7 +135,7 @@ else()
135
135
  list(APPEND {{NAME_PREFIX}}_ADDITIONAL_COMPILE_FLAGS
136
136
  -fsanitize=address
137
137
  -fsanitize=undefined
138
-
138
+
139
139
  )
140
140
  list(APPEND {{NAME_PREFIX}}_ADDITIONAL_LINK_FLAGS
141
141
  -fsanitize=address
@@ -236,6 +236,6 @@ jobs:
236
236
  with:
237
237
  name: Packages
238
238
  path: ${{github.workspace}}/build/download/packages
239
-
239
+
240
240
  - name: Upload to release
241
241
  run: python ./.flow/flow.py ci upload --ref "${{github.action_ref}}" "${{github.workspace}}/build/download/packages"
@@ -25,4 +25,4 @@ _A clear and concise description of the alternative solutions you've considered.
25
25
 
26
26
  ## Additional context
27
27
 
28
- _Add any other context or screenshots about the feature request here._
28
+ _Add any other context or screenshots about the feature request here._
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proj-flow
3
- Version: 0.8.1
3
+ Version: 0.9.0
4
4
  Summary: C++ project maintenance, automated
5
5
  Project-URL: Documentation, https://proj-flow.readthedocs.io/en/latest/
6
6
  Project-URL: Homepage, https://pypi.org/project/proj-flow/
@@ -20,15 +20,16 @@ Requires-Dist: argcomplete
20
20
  Requires-Dist: chevron2021
21
21
  Requires-Dist: prompt-toolkit
22
22
  Requires-Dist: pyyaml
23
+ Requires-Dist: toml
23
24
  Description-Content-Type: text/markdown
24
25
 
25
- # C++ flow
26
+ # Project Flow
26
27
 
27
28
  [![Python package workflow badge](https://github.com/mzdun/proj-flow/actions/workflows/python-publish.yml/badge.svg)](https://github.com/mzdun/proj-flow/actions)
28
29
  [![PyPI version badge](https://img.shields.io/pypi/v/proj-flow.svg)](https://pypi.python.org/pypi/proj-flow)
29
30
  [![PyPI License: MIT](https://img.shields.io/pypi/l/proj-flow.svg)](https://pypi.python.org/pypi/proj-flow)
30
31
 
31
- **C++ flow** aims at being a one-stop tool for C++ projects, from creating new
32
+ **Project Flow** aims at being a one-stop tool for C++ projects, from creating new
32
33
  project, though building and verifying, all the way to publishing releases to
33
34
  the repository. It will run a set of known steps and will happily consult your
34
35
  project what do you want to call any subset of those steps.
@@ -38,13 +39,13 @@ for config and build and GitHub CLI for releases.
38
39
 
39
40
  ## Installation
40
41
 
41
- To create a new project with _C++ flow_, first install it using pip:
42
+ To create a new project with _Project Flow_, first install it using pip:
42
43
 
43
44
  ```sh
44
45
  (.venv) $ pip install proj-flow
45
46
  ```
46
47
 
47
- Every project created with _C++ flow_ has a self-bootstrapping helper script,
48
+ Every project created with _Project Flow_ has a self-bootstrapping helper script,
48
49
  which will install `proj-flow` if it is needed, using either current virtual
49
50
  environment or switching to a private virtual environment (created inside
50
51
  `.flow/.venv` directory). This is used by the GitHub workflow in the generated