Skip to content

Commit ae6b699

Browse files
committed
Complete the basic functions of subpackages.
1 parent ae6df6d commit ae6b699

File tree

28 files changed

+1187
-183
lines changed

28 files changed

+1187
-183
lines changed

.rubisco/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"autoruns": ["git", "test"]
2+
"autoruns": ["subpackages"]
33
}

.rubisco/mirrorlist.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"kgithub": "https://kkgithub.com/${{ user }}/${{ repo }}.git",
66
"githubfast": "https://githubfast.com/${{ user }}/${{ repo }}.git",
77
"bgithub.xyz": "https://bgithub.xyz/${{ user }}/${{ repo }}.git"
8+
},
9+
"ssh": {
10+
"official": "[email protected]:${{ user }}/${{ repo }}.git"
811
}
912
},
1013
"github": "github.com",

extensions/subpackages/subpackages/__init__.py

Lines changed: 117 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,46 @@
2020
"""Rubisco subpackage manager."""
2121

2222
from pathlib import Path
23-
from typing import ClassVar
23+
from typing import Any, ClassVar
2424

25-
from rubisco.kernel.workflow.step import Step
26-
from rubisco.lib.fileutil import find_command
27-
from rubisco.lib.variable import push_variables
25+
from rubisco.shared.api.cefs import (
26+
Argument,
27+
EventCallback,
28+
EventFileData,
29+
EventPath,
30+
Option,
31+
load_callback_args,
32+
)
33+
from rubisco.shared.api.exception import RUValueError
34+
from rubisco.shared.api.kernel import Step
35+
from rubisco.shared.api.l10n import _
36+
from rubisco.shared.api.utils import find_command
37+
from rubisco.shared.api.variable import fast_format_str
2838
from rubisco.shared.extension import IRUExtension
2939
from rubisco.shared.ktrigger import IKernelTrigger
40+
from subpackages.package import Package
3041

3142
__all__ = ["instance"]
3243

3344

45+
def on_fetch(options: list[Option[Any]], args: list[Argument[Any]]) -> None:
46+
"""Fetch git extension."""
47+
opts, _args = load_callback_args(options, args)
48+
protocol = opts.get("protocol", "http")
49+
shallow = opts.get("shallow", True)
50+
use_mirror = opts.get("use-mirror", True)
51+
if protocol not in {"http", "ssh"}:
52+
raise RUValueError(
53+
fast_format_str(
54+
_("Invalid protocol: ${{protocol}}"),
55+
fmt={"protocol": protocol},
56+
),
57+
hint=_('We only support "http"(HTTP(s)) and "ssh"(SSH).'),
58+
)
59+
pkg = Package(Path.cwd())
60+
pkg.fetch(protocol=protocol, shallow=shallow, use_direct=not use_mirror)
61+
62+
3463
class SubpackagesExtension(IRUExtension):
3564
"""Rubisco subpackage manager."""
3665

@@ -48,7 +77,90 @@ def reqs_is_solved(self) -> bool:
4877

4978
def on_load(self) -> None:
5079
"""Load git extension."""
51-
push_variables("test", "TEST")
80+
EventPath("/fetch").mkfile(
81+
EventFileData(
82+
callbacks=[
83+
EventCallback(
84+
callback=on_fetch,
85+
description=_("Fetch subpackages."),
86+
),
87+
],
88+
),
89+
options=[
90+
Option[str](
91+
name="protocol",
92+
title=_("Protocol"),
93+
description=_("Protocol to use for fetching subpackages."),
94+
typecheck=str,
95+
aliases=["p"],
96+
default="http",
97+
ext_attributes={
98+
"cli-advanced-options": [
99+
{
100+
"name": ["--http", "-H"],
101+
"help": _(
102+
"Use HTTP(s) to fetch Git subpackages.",
103+
),
104+
"action": "store_const",
105+
"const": "http",
106+
},
107+
{
108+
"name": ["--ssh", "-S"],
109+
"help": _("Use SSH to fetch Git subpackages."),
110+
"action": "store_const",
111+
"const": "ssh",
112+
},
113+
],
114+
},
115+
),
116+
Option[bool](
117+
name="shallow",
118+
title=_("Shallow"),
119+
description=_("Use shallow clone."),
120+
typecheck=bool,
121+
default=True,
122+
ext_attributes={
123+
"cli-advanced-options": [
124+
{
125+
"name": "--no-shallow",
126+
"help": _(
127+
"Do not use shallow mode to clone Git "
128+
"subpackages.",
129+
),
130+
"action": "store_false",
131+
},
132+
],
133+
},
134+
),
135+
Option[bool](
136+
name="use-mirror",
137+
title=_("Use Mirror"),
138+
description=_(
139+
"Enable mirror speedtest and auto selection.",
140+
),
141+
typecheck=bool,
142+
default=True,
143+
ext_attributes={
144+
"cli-advanced-options": [
145+
{
146+
"name": "-m",
147+
"help": _(
148+
"Use mirror to fetch Git subpackages.",
149+
),
150+
"action": "store_true",
151+
},
152+
{
153+
"name": "-M",
154+
"help": _(
155+
"Disable mirror speedtest and auto selection.", # noqa: E501
156+
),
157+
"action": "store_false",
158+
},
159+
],
160+
},
161+
),
162+
],
163+
)
52164

53165
def solve_reqs(self) -> None:
54166
"""Solve requirements."""

extensions/subpackages/subpackages/package.py

Lines changed: 157 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,97 @@
1919

2020
"""Package class."""
2121

22+
from dataclasses import dataclass
2223
from pathlib import Path
24+
from typing import TYPE_CHECKING
2325

24-
from rubisco.kernel.project_config import (
26+
from rubisco.shared.api.exception import (
27+
RUNotRubiscoProjectError,
28+
RUTypeError,
29+
RUValueError,
30+
)
31+
from rubisco.shared.api.git import git_clone
32+
from rubisco.shared.api.kernel import (
2533
ProjectConfigration,
2634
load_project_config,
2735
)
28-
from rubisco.lib.exceptions import RUNotRubiscoProjectError
29-
from rubisco.lib.variable.fast_format_str import fast_format_str
30-
from rubisco.lib.variable.utils import make_pretty
36+
from rubisco.shared.api.l10n import _
37+
from rubisco.shared.api.variable import fast_format_str
38+
39+
if TYPE_CHECKING:
40+
from rubisco.lib.variable.autoformatdict import AutoFormatDict
41+
42+
43+
@dataclass
44+
class SubpackageReference:
45+
"""Subpackage reference."""
46+
47+
name: str
48+
branch: str
49+
url: str
50+
paths: list[Path]
51+
# We support multiple paths. If one of its path exists, we will use it.
52+
53+
def exists(self) -> bool:
54+
"""Check if the subpackage exists.
55+
56+
Returns:
57+
bool: True if the subpackage exists.
58+
59+
"""
60+
return any(path.exists() for path in self.paths)
61+
62+
def get_path(self) -> Path:
63+
"""Get the path of the subpackage.
64+
65+
Returns:
66+
Path: The path of the subpackage.
67+
68+
"""
69+
for path in self.paths:
70+
if path.exists():
71+
return path
72+
return self.paths[0]
73+
74+
def fetch(
75+
self,
76+
protocol: str,
77+
*,
78+
shallow: bool = True,
79+
use_direct: bool = False,
80+
) -> "Package | None":
81+
"""Fetch the subpackage.
82+
83+
Args:
84+
protocol (str): The protocol to use.
85+
shallow (bool, optional): Whether to use shallow clone. Defaults to
86+
True.
87+
use_direct (bool, optional): Whether to use direct url without
88+
mirror speed test. Defaults to False.
89+
90+
Returns:
91+
Package | None: The fetched package.
92+
93+
"""
94+
path = self.get_path()
95+
git_clone(
96+
self.url,
97+
path,
98+
branch=self.branch,
99+
protocol=protocol,
100+
shallow=shallow,
101+
use_fastest=not use_direct,
102+
)
103+
try:
104+
pkg = Package(path)
105+
pkg.fetch(
106+
protocol=protocol,
107+
shallow=shallow,
108+
use_direct=use_direct,
109+
)
110+
except RUNotRubiscoProjectError:
111+
return None
112+
return pkg
31113

32114

33115
class Package:
@@ -36,7 +118,8 @@ class Package:
36118
name: str
37119
path: Path
38120
config: ProjectConfigration
39-
subpackages: list["Package"]
121+
subpackage_refs: list[SubpackageReference]
122+
subpackages: list["Package | None"]
40123

41124
def __init__(self, path: Path) -> None:
42125
"""Parse a rubisco package.
@@ -45,19 +128,73 @@ def __init__(self, path: Path) -> None:
45128
path (Path): The path of the package.
46129
47130
"""
48-
try:
49-
self.config = load_project_config(path)
50-
except RUNotRubiscoProjectError as exc:
51-
raise RUNotRubiscoProjectError(
52-
fast_format_str(
53-
_(
54-
"Working directory '[underline]${{path}}[/underline]'"
55-
" not a rubisco project.",
131+
config = load_project_config(path)
132+
self.name = config.name
133+
self.path = path
134+
self.config = config
135+
self._load_subpkgs()
136+
137+
def _load_subpkgs(self) -> None:
138+
subpkgs: list[SubpackageReference] = []
139+
subpkg_dict = self.config.config.get("subpackages", [], valtype=dict)
140+
subpkg_dict: AutoFormatDict
141+
for name, subpkg in subpkg_dict.items():
142+
if not isinstance(subpkg, dict): # type: ignore[union-attr]
143+
msg = fast_format_str(
144+
_("Subpackage reference ${{name}} is not a dict."),
145+
fmt={"name": name},
146+
)
147+
raise RUTypeError(msg)
148+
subpkg: AutoFormatDict
149+
branch = subpkg.get("branch", valtype=str)
150+
url = subpkg.get("url", valtype=str)
151+
paths = subpkg.get("path", valtype=list[str] | str)
152+
if isinstance(paths, str):
153+
paths = [paths]
154+
if not paths:
155+
raise RUValueError(
156+
fast_format_str(
157+
_("Subpackage reference ${{name}} has no path."),
158+
fmt={"name": name},
56159
),
57-
fmt={"path": make_pretty(Path.cwd().absolute())},
58-
),
59-
hint=fast_format_str(
60-
_("'[underline]${{path}}[/underline]' is not found."),
61-
fmt={"path": make_pretty(USER_REPO_CONFIG.absolute())},
62-
),
63-
) from exc
160+
)
161+
spr = SubpackageReference(
162+
name=name,
163+
branch=branch,
164+
url=url,
165+
paths=[self.path / Path(p) for p in paths],
166+
)
167+
subpkgs.append(spr)
168+
169+
self.subpackage_refs = subpkgs
170+
171+
def fetch(
172+
self,
173+
protocol: str,
174+
*,
175+
shallow: bool = True,
176+
use_direct: bool = False,
177+
) -> list["Package | None"]:
178+
"""Fetch the package.
179+
180+
Args:
181+
protocol (str): The protocol to use.
182+
shallow (bool, optional): Whether to use shallow clone. Defaults to
183+
True.
184+
use_direct (bool, optional): Whether to use direct url without
185+
mirror speed test. Defaults to False.
186+
187+
Returns:
188+
list[Package | None]: The fetched packages.
189+
190+
"""
191+
subpkgs = [
192+
subpkg.fetch(
193+
protocol,
194+
shallow=shallow,
195+
use_direct=use_direct,
196+
)
197+
for subpkg in self.subpackage_refs
198+
]
199+
self.subpackages = subpkgs
200+
return subpkgs

0 commit comments

Comments
 (0)