proj-flow 0.18.0__py3-none-any.whl → 0.20.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 (33) hide show
  1. proj_flow/__init__.py +1 -1
  2. proj_flow/api/makefile.py +1 -1
  3. proj_flow/ext/tools/__init__.py +13 -0
  4. proj_flow/ext/tools/pragma_once.py +40 -0
  5. proj_flow/ext/tools/run_linter.py +228 -0
  6. proj_flow/ext/webidl/__init__.py +12 -0
  7. proj_flow/ext/webidl/base/__init__.py +2 -0
  8. proj_flow/ext/webidl/base/config.py +425 -0
  9. proj_flow/ext/webidl/cli/__init__.py +10 -0
  10. proj_flow/ext/webidl/cli/cmake.py +82 -0
  11. proj_flow/ext/webidl/cli/depfile.py +83 -0
  12. proj_flow/ext/webidl/cli/gen.py +157 -0
  13. proj_flow/ext/webidl/cli/init.py +26 -0
  14. proj_flow/ext/webidl/cli/root.py +12 -0
  15. proj_flow/ext/webidl/cli/updater.py +20 -0
  16. proj_flow/ext/webidl/data/init/flow_webidl.cmake +26 -0
  17. proj_flow/ext/webidl/data/templates/cmake.mustache +45 -0
  18. proj_flow/ext/webidl/data/templates/depfile.mustache +6 -0
  19. proj_flow/ext/webidl/data/templates/partials/cxx/attribute-decl.mustache +1 -0
  20. proj_flow/ext/webidl/data/templates/partials/cxx/in-out.mustache +2 -0
  21. proj_flow/ext/webidl/data/templates/partials/cxx/includes.mustache +6 -0
  22. proj_flow/ext/webidl/data/templates/partials/cxx/operation-decl.mustache +12 -0
  23. proj_flow/ext/webidl/data/templates/partials/cxx/type.mustache +6 -0
  24. proj_flow/ext/webidl/data/types/cxx.json +47 -0
  25. proj_flow/ext/webidl/model/__init__.py +2 -0
  26. proj_flow/ext/webidl/model/ast.py +586 -0
  27. proj_flow/ext/webidl/model/builders.py +230 -0
  28. proj_flow/ext/webidl/registry.py +23 -0
  29. {proj_flow-0.18.0.dist-info → proj_flow-0.20.0.dist-info}/METADATA +2 -1
  30. {proj_flow-0.18.0.dist-info → proj_flow-0.20.0.dist-info}/RECORD +33 -7
  31. {proj_flow-0.18.0.dist-info → proj_flow-0.20.0.dist-info}/WHEEL +0 -0
  32. {proj_flow-0.18.0.dist-info → proj_flow-0.20.0.dist-info}/entry_points.txt +0 -0
  33. {proj_flow-0.18.0.dist-info → proj_flow-0.20.0.dist-info}/licenses/LICENSE +0 -0
proj_flow/__init__.py CHANGED
@@ -6,4 +6,4 @@ The **proj_flow** contains only ``__version__`` to be updated, nothing more.
6
6
  This is in an attempt to make this module easy to load initially.
7
7
  """
8
8
 
9
- __version__ = "0.18.0"
9
+ __version__ = "0.20.0"
proj_flow/api/makefile.py CHANGED
@@ -73,7 +73,7 @@ class Rule(ABC):
73
73
  @abstractmethod
74
74
  def command(self, statement: Statement) -> List[str]: ...
75
75
 
76
- def run(self, statement: Statement, rt: Runtime):
76
+ def run(self, statement: Statement, rt: Runtime) -> int:
77
77
  return 1
78
78
 
79
79
  @classmethod
@@ -0,0 +1,13 @@
1
+ # Copyright (c) 2026 Marcin Zdun
2
+ # This file is licensed under MIT license (see LICENSE for details)
3
+
4
+ from proj_flow.api import arg
5
+ from proj_flow.ext.tools.pragma_once import pragma_once
6
+ from proj_flow.ext.tools.run_linter import run_linter
7
+
8
+ __all__ = ["tools", "pragma_once", "run_linter"]
9
+
10
+
11
+ @arg.command("tools")
12
+ def tools():
13
+ """Run various C++ tools and helpers"""
@@ -0,0 +1,40 @@
1
+ # Copyright (c) 2026 Marcin Zdun
2
+ # This file is licensed under MIT license (see LICENSE for details)
3
+
4
+ from pathlib import Path
5
+
6
+ from proj_flow.api import arg
7
+
8
+
9
+ def _pragma_present(path: Path):
10
+ with path.open(encoding="UTF-8") as text:
11
+ for line in text:
12
+ line = line.rstrip()
13
+ if line == "#pragma once":
14
+ return True
15
+ return False
16
+
17
+
18
+ @arg.command("tools", "pragma-once")
19
+ def pragma_once():
20
+ """Check presence and absence of ``#pragma once``, as expected"""
21
+
22
+ code_exts = [".c", ".c++", ".cc", ".cpp", ".cxx"]
23
+ header_exts = [".h", ".h++", ".hh", ".hpp", ".hxx"]
24
+
25
+ paths: dict[bool, list[Path]] = {True: [], False: []}
26
+
27
+ for root, dirs, files in Path(".").walk():
28
+ dirs[:] = [dir_name for dir_name in dirs if dir_name not in [".git", "build"]]
29
+ for filename in files:
30
+ path = root / filename
31
+ ext = path.suffix
32
+ if ext in code_exts:
33
+ paths[False].append(path)
34
+ elif ext in header_exts:
35
+ paths[True].append(path)
36
+
37
+ for expected in paths:
38
+ for path in paths[expected]:
39
+ if _pragma_present(path) != expected:
40
+ print(path.as_posix())
@@ -0,0 +1,228 @@
1
+ # Copyright (c) 2026 Marcin Zdun
2
+ # This code is licensed under MIT license (see LICENSE for details)
3
+
4
+ import shlex
5
+ import subprocess
6
+ import urllib.parse
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Annotated, Any, cast
10
+
11
+ import requests_cache
12
+ from yaml import load, safe_load
13
+
14
+ from proj_flow.api.arg import Argument, command
15
+ from proj_flow.api.env import Runtime
16
+
17
+ try:
18
+ from yaml import CLoader as Loader
19
+ except ImportError:
20
+ from yaml import Loader
21
+
22
+ IMAGE = "github/super-linter"
23
+ PREFIX = f"{IMAGE}/"
24
+
25
+
26
+ @dataclass
27
+ class Image:
28
+ repo: str
29
+ name: str
30
+ version: str
31
+
32
+ @property
33
+ def full_name(self):
34
+ return f"{self.repo}/{self.name}"
35
+
36
+ @property
37
+ def label(self):
38
+ return f"{self.repo}/{self.name}:{self.version}"
39
+
40
+ def super_linter_args(self, env: dict[str, str], log_level: str):
41
+ _env = env.copy()
42
+ _env["RUN_LOCAL"] = "true"
43
+ _env["LOG_LEVEL"] = log_level
44
+ _env["USE_FIND_ALGORITHM"] = "true"
45
+ _env["VALIDATE_ALL_CODEBASE"] = "true"
46
+
47
+ args = [
48
+ arg
49
+ for pair in (["-e", f"{key}={_env[key]}"] for key in _env)
50
+ for arg in pair
51
+ ]
52
+ return [
53
+ "docker",
54
+ "run",
55
+ "--rm",
56
+ *args,
57
+ "-v" f"{Path().absolute().as_posix()}:/tmp/lint",
58
+ self.label,
59
+ ]
60
+
61
+
62
+ @dataclass
63
+ class Step:
64
+ name: str
65
+ image: Image
66
+ env: dict[str, str]
67
+
68
+
69
+ @dataclass
70
+ class Job:
71
+ name: str
72
+ steps: list[Step]
73
+
74
+
75
+ @command("tools", "run-linter")
76
+ def run_linter(
77
+ log_level: Annotated[
78
+ str,
79
+ Argument(
80
+ help="Select one of log levels (defaults to INFO)",
81
+ choices=["ERROR", "WARN", "NOTICE", "INFO", "DEBUG"],
82
+ default="INFO",
83
+ opt=True,
84
+ ),
85
+ ],
86
+ rt: Runtime,
87
+ ):
88
+ """Run dockerized super-linter with all the settings from local GitHub workflows"""
89
+
90
+ WORKFLOWS = Path(".github/workflows")
91
+ if not WORKFLOWS.is_dir():
92
+ if not rt.silent:
93
+ print("This project has no GitHub workflows")
94
+ return 0
95
+
96
+ db_path = Path("build")
97
+ db_path.mkdir(parents=True, exist_ok=True)
98
+ db_path = db_path / "cache_db"
99
+
100
+ session = requests_cache.CachedSession(db_path)
101
+ JOBS: list[Job] = []
102
+ for root, dirs, files in WORKFLOWS.walk():
103
+ for filename in files:
104
+ path = root / filename
105
+ if path.suffix not in [".yml", ".yaml"]:
106
+ continue
107
+
108
+ with path.open(encoding="UTF-8") as f:
109
+ data = load(f, Loader=Loader)
110
+ try:
111
+ yml_name = cast(str, data["name"])
112
+ except KeyError:
113
+ yml_name = path.stem
114
+ JOBS.extend(_get_linter_actions(data, yml_name, session, rt.verbose))
115
+
116
+ for job in JOBS:
117
+ job_name, steps = job.name, job.steps
118
+ print(f"job: \033[1;49;95m{job_name}\033[m")
119
+ for step in steps:
120
+ print(
121
+ f"step: \033[1;49;96m{step.name}\033[m \033[2;49;32m({step.image.full_name}:\033[0;49;32m{step.image.version}\033[2;49;32m)\033[m"
122
+ )
123
+ args = step.image.super_linter_args(step.env, log_level=log_level)
124
+ print("\033[0;49;30m{}\033[m".format(shlex.join(args)))
125
+ subprocess.run(args, shell=False)
126
+ print()
127
+
128
+
129
+ def _download_docker_image_id(
130
+ action: str,
131
+ version: str | None,
132
+ session: requests_cache.CachedSession,
133
+ verbose: bool,
134
+ ):
135
+ if action != IMAGE and not action.startswith(PREFIX):
136
+ return None
137
+
138
+ repo = "https://raw.githubusercontent.com/github/super-linter"
139
+ refs = f"refs/tags/{version}" if version else "refs/heads/main"
140
+ filename = "action.yml"
141
+ if action.startswith(PREFIX):
142
+ filename = f"{action[len(PREFIX):]}/{filename}"
143
+
144
+ url = f"{repo}/{refs}/{filename}"
145
+ if verbose:
146
+ print("-- run-linter: Downloading", url)
147
+ response = session.get(url)
148
+ response.raise_for_status()
149
+ data = cast(dict[str, dict[str, str]], safe_load(response.text))
150
+ runs = data.get("runs", {})
151
+ if runs.get("using") != "docker":
152
+ return None
153
+
154
+ image = runs.get("image")
155
+ if not image:
156
+ return None
157
+
158
+ url = urllib.parse.urlparse(image)
159
+ name, version = url.path[1:].split(":", 1)
160
+ return Image(url.netloc, name, version)
161
+
162
+
163
+ def _get_linter_action(
164
+ step: dict, session: requests_cache.CachedSession, verbose: bool
165
+ ):
166
+ try:
167
+ id_parts = cast(str, step["uses"]).split("@", 1)
168
+ except KeyError:
169
+ return
170
+
171
+ action = id_parts[0]
172
+ action_version = id_parts[1] if len(id_parts) > 1 else None
173
+ image = _download_docker_image_id(action, action_version, session, verbose)
174
+ if not image:
175
+ return
176
+
177
+ try:
178
+ name = cast(str, step["name"])
179
+ except KeyError:
180
+ name = cast(str, step["uses"])
181
+
182
+ if verbose:
183
+ print(f'-- run-linter: Found {image.label} used by "{name}"')
184
+
185
+ try:
186
+ env = cast(dict[str, Any], step["env"])
187
+ except KeyError:
188
+ env = {}
189
+
190
+ _env: dict[str, str] = {}
191
+ for key in env:
192
+ value = env[key]
193
+ if isinstance(value, bool):
194
+ _env[key] = "true" if value else "false"
195
+ continue
196
+ if isinstance(value, str) and "${{" in value:
197
+ continue
198
+ _env[key] = value
199
+
200
+ return Step(name=name, image=image, env=_env)
201
+
202
+
203
+ def _get_linter_actions(
204
+ data: dict, yml_name: str, session: requests_cache.CachedSession, verbose: bool
205
+ ):
206
+ result: list[Job] = []
207
+ for job_id, job in cast(dict[str, dict], data["jobs"]).items():
208
+ steps: list[Step] = []
209
+ for step_def in cast(list[dict], job["steps"]):
210
+ step = _get_linter_action(step_def, session, verbose)
211
+ if step is None:
212
+ continue
213
+ steps.append(step)
214
+
215
+ if not steps:
216
+ continue
217
+
218
+ try:
219
+ name = cast(str, job["name"])
220
+ except KeyError:
221
+ name = job_id
222
+
223
+ if name != yml_name:
224
+ name = f"{yml_name} » {name}"
225
+
226
+ result.append(Job(name=name, steps=steps))
227
+
228
+ return result
@@ -0,0 +1,12 @@
1
+ # Copyright (c) 2026 Marcin Zdun
2
+ # This code is licensed under MIT license (see LICENSE for details)
3
+
4
+ """
5
+ The **proj_flow.ext.webidl** provides ``webidl init``, ``webidl cmake``,
6
+ ``webidl depfile``and ``webidl gen`` commands, allowing for simple
7
+ WebIDL/mustache generation.
8
+ """
9
+
10
+ from . import cli
11
+
12
+ __all__ = ["cli"]
@@ -0,0 +1,2 @@
1
+ # Copyright (c) 2026 Marcin Zdun
2
+ # This code is licensed under MIT license (see LICENSE for details)