build(baseline): add lib/jmap.py Joomla manifest-mapper + tests
Generic Joomla 3.x manifest -> deployed-path mapper (component/module/plugin/ library/template/package), used to author the pristine (upstream) commit of a packages/<slug> module before it is reconciled to the live bytes. 18/18 tests green; ruff/mypy-strict clean. Signed-off-by: LÁZÁR Imre <imre@illusion.hu> Assisted-by: claude-code@claude-opus-4-8
This commit is contained in:
parent
2b076db480
commit
0c71320ee8
151
lib/jmap.py
151
lib/jmap.py
@ -19,6 +19,7 @@ Csak a Python standard library-t használja (nincs külső dependency).
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
@ -80,22 +81,90 @@ def _extension_name(root: ET.Element) -> str:
|
|||||||
def _plugin_element(root: ET.Element) -> str:
|
def _plugin_element(root: ET.Element) -> str:
|
||||||
"""A plugin element-slugját adja vissza.
|
"""A plugin element-slugját adja vissza.
|
||||||
|
|
||||||
Elsődleges forrás: ``<files><filename plugin="...">`` attribútum (ez a
|
Elsődleges forrás: ``<files>`` blokk ``<filename plugin="...">`` vagy
|
||||||
Joomla-konvenció szerinti autoritatív érték). Ha ez hiányzik, a
|
``<file plugin="...">`` gyereke (mindkét tag-alak előfordul a valós
|
||||||
``<name>PLG_GROUP_ELEMENT</name>`` konvenció utolsó '_'-tagját használja
|
csomagokban -- ez a Joomla-konvenció szerinti autoritatív érték). Ha ez
|
||||||
fallback-ként.
|
hiányzik, a ``<name>PLG_GROUP_ELEMENT</name>`` konvenció utolsó
|
||||||
|
'_'-tagját használja fallback-ként.
|
||||||
"""
|
"""
|
||||||
files_elem = root.find("files")
|
files_elem = root.find("files")
|
||||||
if files_elem is not None:
|
if files_elem is not None:
|
||||||
for filename_elem in files_elem.findall("filename"):
|
for child in files_elem:
|
||||||
plugin_attr = filename_elem.get("plugin")
|
if child.tag not in ("filename", "file"):
|
||||||
|
continue
|
||||||
|
plugin_attr = child.get("plugin")
|
||||||
if plugin_attr:
|
if plugin_attr:
|
||||||
return plugin_attr.strip()
|
return plugin_attr.strip()
|
||||||
name_text = _text(root.find("name"))
|
name_text = _text(root.find("name"))
|
||||||
if name_text:
|
if name_text:
|
||||||
return _slug(name_text.rsplit("_", 1)[-1])
|
return _slug(name_text.rsplit("_", 1)[-1])
|
||||||
raise JoomlaManifestError(
|
raise JoomlaManifestError(
|
||||||
'nem határozható meg a plugin element-je: nincs <filename plugin="..."> és nincs <name>'
|
'nem határozható meg a plugin element-je: nincs <filename plugin="..."> / '
|
||||||
|
'<file plugin="..."> és nincs <name>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_COMPONENT_LANG_SYS_INI = re.compile(r"(com_[a-z0-9_]+)\.sys\.ini$", re.IGNORECASE)
|
||||||
|
_COMPONENT_SCRIPTFILE = re.compile(r"script\.([a-z0-9_]+)\.php$", re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
|
def _component_slug(text: str) -> str:
|
||||||
|
"""Komponens-display-nevet ('OneAll Social Login') Joomla-konvenció szerint
|
||||||
|
normalizál a végső fallback-ágon: szóköz-ELTÁVOLÍTÁS (nem aláhúzás-csere)
|
||||||
|
+ kisbetűsítés -> 'oneallsociallogin'.
|
||||||
|
"""
|
||||||
|
return "".join(text.strip().lower().split())
|
||||||
|
|
||||||
|
|
||||||
|
def _component_name(root: ET.Element) -> str:
|
||||||
|
"""A komponens deployed-elemnevét ('com_X') vezeti le a Joomla 3.x
|
||||||
|
installer-heurisztika prioritási sorrendjében:
|
||||||
|
|
||||||
|
1. explicit gyökér ``element`` attribútum
|
||||||
|
2. a fő ``<files folder="com_...">`` attribútuma, ha 'com_'-prefixű --
|
||||||
|
ez az autoritatív forrás, mert a ``<name>`` gyakran csak emberi
|
||||||
|
megjelenítő-cím (pl. "OneAll Social Login", nem "com_oneallsociallogin")
|
||||||
|
3. az adminisztratív, majd a site ``<languages><language>`` fájlnév-
|
||||||
|
mintázata ``en-GB.com_<elem>.sys.ini`` -> ``com_<elem>``
|
||||||
|
4. ``<scriptfile>install/script.<elem>.php</scriptfile>`` -> ``com_<elem>``
|
||||||
|
5. végső fallback: ``<name>`` Joomla-konvenció szerinti normalizálása
|
||||||
|
(szóköz-eltávolítás, kisbetűsítés); ha az eredmény már 'com_'-prefixű
|
||||||
|
(gépi-nevet tartalmazó manifest), változatlanul megy tovább, egyébként
|
||||||
|
'com_' prefixet kap.
|
||||||
|
"""
|
||||||
|
element_attr = root.get("element")
|
||||||
|
if element_attr:
|
||||||
|
return element_attr.strip()
|
||||||
|
|
||||||
|
files_elem = root.find("files")
|
||||||
|
if files_elem is not None:
|
||||||
|
folder_attr = files_elem.get("folder", "")
|
||||||
|
if folder_attr.lower().startswith("com_"):
|
||||||
|
return folder_attr
|
||||||
|
|
||||||
|
for languages_elem in (root.find("administration/languages"), root.find("languages")):
|
||||||
|
if languages_elem is None:
|
||||||
|
continue
|
||||||
|
for language_elem in languages_elem.findall("language"):
|
||||||
|
match = _COMPONENT_LANG_SYS_INI.search(_text(language_elem))
|
||||||
|
if match:
|
||||||
|
return match.group(1).lower()
|
||||||
|
|
||||||
|
scriptfile_text = _text(root.find("scriptfile"))
|
||||||
|
if scriptfile_text:
|
||||||
|
match = _COMPONENT_SCRIPTFILE.search(scriptfile_text)
|
||||||
|
if match:
|
||||||
|
return f"com_{match.group(1).lower()}"
|
||||||
|
|
||||||
|
name_text = _text(root.find("name"))
|
||||||
|
if name_text:
|
||||||
|
slug = _component_slug(name_text)
|
||||||
|
return slug if slug.startswith("com_") else f"com_{slug}"
|
||||||
|
|
||||||
|
raise JoomlaManifestError(
|
||||||
|
"nem határozható meg a komponens deployed-neve: nincs 'element' attribútum, "
|
||||||
|
'nincs <files folder="com_...">, nincs com_-mintázatú nyelvi fájl, '
|
||||||
|
"nincs <scriptfile>, és nincs <name>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -105,10 +174,14 @@ def _plugin_element(root: ET.Element) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _apply_files(files_elem: ET.Element | None, manifest_dir: Path, dest_dir: Path) -> list[CopyOp]:
|
def _apply_files(files_elem: ET.Element | None, manifest_dir: Path, dest_dir: Path) -> list[CopyOp]:
|
||||||
"""Egy ``<files>``-szerű blokk (files/media) gyerekeit (filename/folder)
|
"""Egy ``<files>``-szerű blokk (files/media) gyerekeit CopyOp-listává alakítja.
|
||||||
CopyOp-listává alakítja. A ``folder`` attribútum a forrás-alkönyvtárat
|
|
||||||
jelöli a manifest könyvtárához képest; a ``target`` attribútum (ha van)
|
Az egyedi-fájl gyerek tag-neve a valós csomagokban ``<filename>`` VAGY
|
||||||
felülírja a cél-relatív-útvonalat.
|
``<file>`` lehet (mindkettőt elfogadja a Joomla-installer, csak a szöveg-
|
||||||
|
tartalom és a ``target``/egyéb attribútumok számítanak); a ``<folder>``
|
||||||
|
gyerek egy teljes alfát másol. A ``folder`` attribútum a forrás-
|
||||||
|
alkönyvtárat jelöli a manifest könyvtárához képest; a ``target``
|
||||||
|
attribútum (ha van) felülírja a cél-relatív-útvonalat.
|
||||||
"""
|
"""
|
||||||
if files_elem is None:
|
if files_elem is None:
|
||||||
return []
|
return []
|
||||||
@ -116,7 +189,7 @@ def _apply_files(files_elem: ET.Element | None, manifest_dir: Path, dest_dir: Pa
|
|||||||
src_base = manifest_dir / folder_attr if folder_attr else manifest_dir
|
src_base = manifest_dir / folder_attr if folder_attr else manifest_dir
|
||||||
ops: list[CopyOp] = []
|
ops: list[CopyOp] = []
|
||||||
for child in files_elem:
|
for child in files_elem:
|
||||||
if child.tag not in ("filename", "folder"):
|
if child.tag not in ("filename", "file", "folder"):
|
||||||
continue
|
continue
|
||||||
rel_text = _text(child)
|
rel_text = _text(child)
|
||||||
if not rel_text:
|
if not rel_text:
|
||||||
@ -178,8 +251,14 @@ def _apply_languages(languages_elem: ET.Element | None, manifest_dir: Path, lang
|
|||||||
def map_component(root: ET.Element, manifest_dir: Path, out_root: Path) -> list[CopyOp]:
|
def map_component(root: ET.Element, manifest_dir: Path, out_root: Path) -> list[CopyOp]:
|
||||||
"""com_X: files->components/com_X, administration/files->administrator/components/com_X,
|
"""com_X: files->components/com_X, administration/files->administrator/components/com_X,
|
||||||
media->media/com_X, languages(site+admin)->language/ ill. administrator/language/.
|
media->media/com_X, languages(site+admin)->language/ ill. administrator/language/.
|
||||||
|
|
||||||
|
A manifestbe ágyazott ``<modules>``/``<plugins>`` szekciók (ha vannak --
|
||||||
|
pl. egy komponens saját manifestje telepít egy modult/plugint is, mint
|
||||||
|
az OneAll Social Login csomagnál) is feldolgozásra kerülnek; ez NEM a
|
||||||
|
``pkg_`` package-típus (annak külön al-manifestjei vannak), hanem a
|
||||||
|
komponens EGYETLEN manifestjébe ágyazott mapping-szekció.
|
||||||
"""
|
"""
|
||||||
name = _extension_name(root)
|
name = _component_name(root)
|
||||||
ops = _apply_files(root.find("files"), manifest_dir, out_root / "components" / name)
|
ops = _apply_files(root.find("files"), manifest_dir, out_root / "components" / name)
|
||||||
|
|
||||||
admin_elem = root.find("administration")
|
admin_elem = root.find("administration")
|
||||||
@ -193,6 +272,52 @@ def map_component(root: ET.Element, manifest_dir: Path, out_root: Path) -> list[
|
|||||||
|
|
||||||
ops += _apply_media(root.find("media"), manifest_dir, out_root / "media")
|
ops += _apply_media(root.find("media"), manifest_dir, out_root / "media")
|
||||||
ops += _apply_languages(root.find("languages"), manifest_dir, out_root / "language")
|
ops += _apply_languages(root.find("languages"), manifest_dir, out_root / "language")
|
||||||
|
ops += _apply_embedded_modules(root.find("modules"), manifest_dir, out_root)
|
||||||
|
ops += _apply_embedded_plugins(root.find("plugins"), manifest_dir, out_root)
|
||||||
|
return ops
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_embedded_modules(
|
||||||
|
modules_elem: ET.Element | None, manifest_dir: Path, out_root: Path
|
||||||
|
) -> list[CopyOp]:
|
||||||
|
"""Egy komponens-manifestbe ágyazott ``<modules><module module="mod_X" client="...">``
|
||||||
|
szekció feldolgozása. A deployed-modulnév közvetlenül a ``module`` attribútumból jön
|
||||||
|
(nem a gyakran emberi-olvasható ``<name>``-ből).
|
||||||
|
"""
|
||||||
|
if modules_elem is None:
|
||||||
|
return []
|
||||||
|
ops: list[CopyOp] = []
|
||||||
|
for module_elem in modules_elem.findall("module"):
|
||||||
|
module_name = module_elem.get("module")
|
||||||
|
if not module_name:
|
||||||
|
raise JoomlaManifestError(f"beágyazott <module> hiányzó 'module' attribútummal: {manifest_dir}")
|
||||||
|
is_admin = module_elem.get("client") == "administrator"
|
||||||
|
base = out_root / ("administrator/modules" if is_admin else "modules") / module_name
|
||||||
|
ops += _apply_files(module_elem.find("files"), manifest_dir, base)
|
||||||
|
ops += _apply_media(module_elem.find("media"), manifest_dir, out_root / "media")
|
||||||
|
return ops
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_embedded_plugins(
|
||||||
|
plugins_elem: ET.Element | None, manifest_dir: Path, out_root: Path
|
||||||
|
) -> list[CopyOp]:
|
||||||
|
"""Egy komponens-manifestbe ágyazott ``<plugins><plugin plugin="X" group="Y">``
|
||||||
|
szekció feldolgozása. Az elem-slug és a group közvetlenül a ``plugin``/``group``
|
||||||
|
attribútumokból jön (nem a gyakran emberi-olvasható ``<name>``-ből).
|
||||||
|
"""
|
||||||
|
if plugins_elem is None:
|
||||||
|
return []
|
||||||
|
ops: list[CopyOp] = []
|
||||||
|
for plugin_elem in plugins_elem.findall("plugin"):
|
||||||
|
element = plugin_elem.get("plugin")
|
||||||
|
group = plugin_elem.get("group")
|
||||||
|
if not element or not group:
|
||||||
|
raise JoomlaManifestError(
|
||||||
|
f"beágyazott <plugin> hiányzó 'plugin' vagy 'group' attribútummal: {manifest_dir}"
|
||||||
|
)
|
||||||
|
base = out_root / "plugins" / group / element
|
||||||
|
ops += _apply_files(plugin_elem.find("files"), manifest_dir, base)
|
||||||
|
ops += _apply_media(plugin_elem.find("media"), manifest_dir, out_root / "media")
|
||||||
return ops
|
return ops
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -80,6 +80,134 @@ def test_component_site_admin_media_language(tmp_path: Path) -> None:
|
|||||||
assert (out / "administrator/language/en-GB/en-GB.com_hello.sys.ini").read_text() == "COM_HELLO=Hello"
|
assert (out / "administrator/language/en-GB/en-GB.com_hello.sys.ini").read_text() == "COM_HELLO=Hello"
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# component deployed-név-levezetés -- regressziós tesztek a valós
|
||||||
|
# OneAll Social Login csomagon talált hibára (a <name> emberi megjelenítő-cím
|
||||||
|
# volt, nem a gépi elemnév; a helyes forrás a <files folder="com_..."> ill.
|
||||||
|
# a nyelvi fájlnév/scriptfile-mintázat/name-fallback prioritási láncban).
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_component_name_from_files_folder_com_prefix(tmp_path: Path) -> None:
|
||||||
|
"""Elsődleges forrás: <files folder="com_..."> -- a <name> emberi cím, eltér."""
|
||||||
|
pkg = tmp_path / "pkg"
|
||||||
|
out = tmp_path / "out"
|
||||||
|
_write(
|
||||||
|
pkg / "manifest.xml",
|
||||||
|
"""<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<extension type="component" version="3">
|
||||||
|
<name>Totally Human Display Title</name>
|
||||||
|
<files folder="com_realname">
|
||||||
|
<file>index.html</file>
|
||||||
|
</files>
|
||||||
|
</extension>
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
_write(pkg / "com_realname/index.html", "<html></html>")
|
||||||
|
|
||||||
|
_run(pkg, out)
|
||||||
|
|
||||||
|
assert (out / "components/com_realname/index.html").read_text() == "<html></html>"
|
||||||
|
assert not (out / "components/totallyhumandisplaytitle").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_component_name_fallback_removes_spaces_not_underscore(tmp_path: Path) -> None:
|
||||||
|
"""Végső fallback (nincs com_-folder, nincs nyelvi fájl, nincs scriptfile):
|
||||||
|
a <name> normalizálása szóköz-ELTÁVOLÍTÁSSAL történik, NEM aláhúzás-cserével.
|
||||||
|
"""
|
||||||
|
pkg = tmp_path / "pkg"
|
||||||
|
out = tmp_path / "out"
|
||||||
|
_write(
|
||||||
|
pkg / "manifest.xml",
|
||||||
|
"""<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<extension type="component" version="3">
|
||||||
|
<name>OneAll Social Login</name>
|
||||||
|
<files>
|
||||||
|
<filename>index.html</filename>
|
||||||
|
</files>
|
||||||
|
</extension>
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
_write(pkg / "index.html", "<html></html>")
|
||||||
|
|
||||||
|
_run(pkg, out)
|
||||||
|
|
||||||
|
assert (out / "components/com_oneallsociallogin/index.html").exists()
|
||||||
|
assert not (out / "components/com_oneall_social_login").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_component_with_embedded_modules_and_plugins_real_pattern(tmp_path: Path) -> None:
|
||||||
|
"""A valós OneAll Social Login csomag mintázatának replikája: EGYETLEN
|
||||||
|
type="component" manifest, ami <files folder="com_..."> -val adja meg a
|
||||||
|
deployed-nevet, <file> tag-eket (nem <filename>-t) használ, és beágyazott
|
||||||
|
<modules>/<plugins> szekciókat tartalmaz (NEM pkg_ package -- nincs
|
||||||
|
külön al-manifest, minden egy fájlban van).
|
||||||
|
"""
|
||||||
|
pkg = tmp_path / "pkg"
|
||||||
|
out = tmp_path / "out"
|
||||||
|
|
||||||
|
_write(
|
||||||
|
pkg / "manifest.xml",
|
||||||
|
"""<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<extension type="component" version="3" method="upgrade">
|
||||||
|
<name>OneAll Social Login</name>
|
||||||
|
<scriptfile>install/script.oneallsociallogin.php</scriptfile>
|
||||||
|
|
||||||
|
<files folder="com_oneallsociallogin">
|
||||||
|
<file>index.html</file>
|
||||||
|
<file>oneallsociallogin.php</file>
|
||||||
|
</files>
|
||||||
|
|
||||||
|
<administration>
|
||||||
|
<menu>COM_ONEALLSOCIALLOGIN</menu>
|
||||||
|
<files folder="admin">
|
||||||
|
<file>controller.php</file>
|
||||||
|
</files>
|
||||||
|
<languages folder="admin/language">
|
||||||
|
<language tag="en-GB">en-GB.com_oneallsociallogin.sys.ini</language>
|
||||||
|
</languages>
|
||||||
|
</administration>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module module="mod_oneallsociallogin" client="site">
|
||||||
|
<files folder="mod_oneallsociallogin">
|
||||||
|
<file module="mod_oneallsociallogin">mod_oneallsociallogin.php</file>
|
||||||
|
</files>
|
||||||
|
</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
<plugin plugin="oneallsociallogin" group="system">
|
||||||
|
<files folder="plg_oneallsociallogin">
|
||||||
|
<file plugin="oneallsociallogin">oneallsociallogin.php</file>
|
||||||
|
</files>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</extension>
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
_write(pkg / "com_oneallsociallogin/index.html", "<html></html>")
|
||||||
|
_write(pkg / "com_oneallsociallogin/oneallsociallogin.php", "<?php // component")
|
||||||
|
_write(pkg / "admin/controller.php", "<?php // admin controller")
|
||||||
|
_write(pkg / "admin/language/en-GB.com_oneallsociallogin.sys.ini", "COM_ONEALLSOCIALLOGIN=OneAll")
|
||||||
|
_write(pkg / "mod_oneallsociallogin/mod_oneallsociallogin.php", "<?php // module")
|
||||||
|
_write(pkg / "plg_oneallsociallogin/oneallsociallogin.php", "<?php // plugin")
|
||||||
|
|
||||||
|
_run(pkg, out)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
out / "components/com_oneallsociallogin/oneallsociallogin.php"
|
||||||
|
).read_text() == "<?php // component"
|
||||||
|
assert (
|
||||||
|
out / "administrator/components/com_oneallsociallogin/controller.php"
|
||||||
|
).read_text() == "<?php // admin controller"
|
||||||
|
assert (
|
||||||
|
out / "administrator/language/en-GB/en-GB.com_oneallsociallogin.sys.ini"
|
||||||
|
).read_text() == "COM_ONEALLSOCIALLOGIN=OneAll"
|
||||||
|
assert (out / "modules/mod_oneallsociallogin/mod_oneallsociallogin.php").read_text() == "<?php // module"
|
||||||
|
assert (out / "plugins/system/oneallsociallogin/oneallsociallogin.php").read_text() == "<?php // plugin"
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# module: site files + media
|
# module: site files + media
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@ -389,7 +517,10 @@ def test_cli_end_to_end(tmp_path: Path) -> None:
|
|||||||
)
|
)
|
||||||
_write(pkg / "helloworld.php", "<?php // cli")
|
_write(pkg / "helloworld.php", "<?php // cli")
|
||||||
|
|
||||||
script = Path(__file__).parent / "jmap.py"
|
# A jmap-modul tényleges fájl-útvonalát az importált modulból vesszük,
|
||||||
|
# nem a teszt-fájl mellől -- a build-repóban jmap.py a lib/-ben,
|
||||||
|
# test_jmap.py a tests/-ben él, a két fájl NEM ugyanabban a könyvtárban.
|
||||||
|
script = Path(jmap.__file__)
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[sys.executable, str(script), "--package", str(pkg), "--out", str(out)],
|
[sys.executable, str(script), "--package", str(pkg), "--out", str(out)],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
@ -407,7 +538,10 @@ def test_cli_reports_error_exit_code(tmp_path: Path) -> None:
|
|||||||
pkg.mkdir()
|
pkg.mkdir()
|
||||||
out = tmp_path / "out"
|
out = tmp_path / "out"
|
||||||
|
|
||||||
script = Path(__file__).parent / "jmap.py"
|
# A jmap-modul tényleges fájl-útvonalát az importált modulból vesszük,
|
||||||
|
# nem a teszt-fájl mellől -- a build-repóban jmap.py a lib/-ben,
|
||||||
|
# test_jmap.py a tests/-ben él, a két fájl NEM ugyanabban a könyvtárban.
|
||||||
|
script = Path(jmap.__file__)
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[sys.executable, str(script), "--package", str(pkg), "--out", str(out)],
|
[sys.executable, str(script), "--package", str(pkg), "--out", str(out)],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user