|
4 | 4 | from typing import List |
5 | 5 |
|
6 | 6 | import click |
| 7 | +import re |
7 | 8 |
|
8 | 9 | import databusclient.api.deploy as api_deploy |
9 | 10 | from databusclient.api.delete import delete as api_delete |
@@ -213,5 +214,51 @@ def delete(databusuris: List[str], databus_key: str, dry_run: bool, force: bool) |
213 | 214 | ) |
214 | 215 |
|
215 | 216 |
|
| 217 | +@app.command() |
| 218 | +@click.argument("url") |
| 219 | +@click.option("--cv", "cvs", multiple=True, help="Content variant like key=value (repeatable). Keys must not contain '|' or '_'") |
| 220 | +@click.option("--format", "file_format", help="Format extension (e.g. ttl)") |
| 221 | +@click.option("--compression", help="Compression (e.g. gzip)") |
| 222 | +@click.option("--sha-length", help="sha256:length (64 hex chars followed by ':' and integer length)") |
| 223 | +@click.option("--json-output", is_flag=True, help="Output JSON distribution object instead of plain string") |
| 224 | +def mkdist(url, cvs, file_format, compression, sha_length, json_output): |
| 225 | + """Create a distribution string from components.""" |
| 226 | + # Validate CVs |
| 227 | + cvs_dict = {} |
| 228 | + for cv in cvs: |
| 229 | + if "=" not in cv: |
| 230 | + raise click.BadParameter(f"Invalid content variant '{cv}': expected key=value") |
| 231 | + key, val = cv.split("=", 1) |
| 232 | + if any(ch in key for ch in ("|", "_")): |
| 233 | + raise click.BadParameter("Invalid characters in content-variant key (forbidden: '|' and '_')") |
| 234 | + if key in cvs_dict: |
| 235 | + raise click.BadParameter(f"Duplicate content-variant key '{key}'") |
| 236 | + cvs_dict[key] = val |
| 237 | + |
| 238 | + # Validate sha-length |
| 239 | + sha_tuple = None |
| 240 | + if sha_length: |
| 241 | + if not re.match(r'^[A-Fa-f0-9]{64}:\d+$', sha_length): |
| 242 | + raise click.BadParameter("Invalid --sha-length; expected SHA256HEX:length") |
| 243 | + sha, length = sha_length.split(":", 1) |
| 244 | + sha_tuple = (sha, int(length)) |
| 245 | + |
| 246 | + # Deterministic ordering |
| 247 | + sorted_cvs = {k: cvs_dict[k] for k in sorted(cvs_dict)} |
| 248 | + |
| 249 | + dist = api_deploy.create_distribution(url=url, cvs=sorted_cvs, file_format=file_format, compression=compression, sha256_length_tuple=sha_tuple) |
| 250 | + if json_output: |
| 251 | + import json as _json |
| 252 | + click.echo(_json.dumps({"distribution": dist})) |
| 253 | + else: |
| 254 | + click.echo(dist) |
| 255 | + |
| 256 | + |
| 257 | +@app.command() |
| 258 | +@click.argument("shell", type=click.Choice(["bash","zsh","fish","powershell"]), required=False) |
| 259 | +def completion(shell="bash"): |
| 260 | + click.echo(f"Run: eval \"$(_DATABUSCLIENT_COMPLETE=source_{shell} python -m databusclient)\"") |
| 261 | + |
| 262 | + |
216 | 263 | if __name__ == "__main__": |
217 | 264 | app() |
0 commit comments