mdify-cli 2.8.0__tar.gz → 2.9.1__tar.gz
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.
- {mdify_cli-2.8.0/mdify_cli.egg-info → mdify_cli-2.9.1}/PKG-INFO +51 -4
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/README.md +50 -3
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/mdify/__init__.py +1 -1
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/mdify/cli.py +154 -23
- {mdify_cli-2.8.0 → mdify_cli-2.9.1/mdify_cli.egg-info}/PKG-INFO +51 -4
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/pyproject.toml +1 -1
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/tests/test_cli.py +336 -37
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/LICENSE +0 -0
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/assets/mdify.png +0 -0
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/mdify/__main__.py +0 -0
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/mdify/container.py +0 -0
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/mdify/docling_client.py +0 -0
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/mdify_cli.egg-info/SOURCES.txt +0 -0
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/mdify_cli.egg-info/dependency_links.txt +0 -0
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/mdify_cli.egg-info/entry_points.txt +0 -0
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/mdify_cli.egg-info/requires.txt +0 -0
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/mdify_cli.egg-info/top_level.txt +0 -0
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/setup.cfg +0 -0
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/tests/test_container.py +0 -0
- {mdify_cli-2.8.0 → mdify_cli-2.9.1}/tests/test_docling_client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mdify-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.9.1
|
|
4
4
|
Summary: Convert PDFs and document images into structured Markdown for LLM workflows
|
|
5
5
|
Author: tiroq
|
|
6
6
|
License-Expression: MIT
|
|
@@ -42,7 +42,10 @@ A lightweight CLI for converting documents to Markdown. The CLI is fast to insta
|
|
|
42
42
|
## Requirements
|
|
43
43
|
|
|
44
44
|
- **Python 3.8+**
|
|
45
|
-
- **Docker** or
|
|
45
|
+
- **Docker**, **Podman**, or native macOS container tools (for document conversion)
|
|
46
|
+
- On macOS: Supports Apple Container (macOS 26+), OrbStack, Colima, Podman, or Docker Desktop
|
|
47
|
+
- On Linux: Docker or Podman
|
|
48
|
+
- Auto-detects available tools
|
|
46
49
|
|
|
47
50
|
## Installation
|
|
48
51
|
|
|
@@ -56,6 +59,13 @@ pipx install mdify-cli
|
|
|
56
59
|
|
|
57
60
|
Restart your terminal after installation.
|
|
58
61
|
|
|
62
|
+
For containerized document conversion, install one of these (or use Docker Desktop):
|
|
63
|
+
- **Apple Container** (macOS 26+): Download from https://github.com/apple/container/releases
|
|
64
|
+
- **OrbStack** (recommended): `brew install orbstack`
|
|
65
|
+
- **Colima**: `brew install colima && colima start`
|
|
66
|
+
- **Podman**: `brew install podman && podman machine init && podman machine start`
|
|
67
|
+
- **Docker Desktop**: Available at https://www.docker.com/products/docker-desktop
|
|
68
|
+
|
|
59
69
|
### Linux
|
|
60
70
|
|
|
61
71
|
```bash
|
|
@@ -142,13 +152,50 @@ The first conversion takes longer (~30-60s) as the container loads ML models int
|
|
|
142
152
|
| `-m, --mask` | ⚠️ **Deprecated**: PII masking not supported in current version |
|
|
143
153
|
| `--gpu` | Use GPU-accelerated container (requires NVIDIA GPU and nvidia-container-toolkit) |
|
|
144
154
|
| `--port PORT` | Container port (default: 5001) |
|
|
145
|
-
| `--runtime RUNTIME` | Container runtime: docker or
|
|
155
|
+
| `--runtime RUNTIME` | Container runtime: docker, podman, orbstack, colima, or container (auto-detected) |
|
|
146
156
|
| `--image IMAGE` | Custom container image (default: ghcr.io/docling-project/docling-serve-cpu:main) |
|
|
147
157
|
| `--pull POLICY` | Image pull policy: always, missing, never (default: missing) |
|
|
148
158
|
| `--check-update` | Check for available updates and exit |
|
|
149
159
|
| `--version` | Show version and exit |
|
|
150
160
|
|
|
151
|
-
###
|
|
161
|
+
### Container Runtime Selection
|
|
162
|
+
|
|
163
|
+
mdify automatically detects and uses the best available container runtime. The detection order differs by platform:
|
|
164
|
+
|
|
165
|
+
**macOS (recommended):**
|
|
166
|
+
1. Apple Container (native, macOS 26+ required)
|
|
167
|
+
2. OrbStack (lightweight, fast)
|
|
168
|
+
3. Colima (open-source alternative)
|
|
169
|
+
4. Podman (via Podman machine)
|
|
170
|
+
5. Docker Desktop (full Docker)
|
|
171
|
+
|
|
172
|
+
**Linux:**
|
|
173
|
+
1. Docker
|
|
174
|
+
2. Podman
|
|
175
|
+
|
|
176
|
+
**Override runtime:**
|
|
177
|
+
Use the `MDIFY_CONTAINER_RUNTIME` environment variable to force a specific runtime:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
export MDIFY_CONTAINER_RUNTIME=orbstack
|
|
181
|
+
mdify document.pdf
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Or inline:
|
|
185
|
+
```bash
|
|
186
|
+
MDIFY_CONTAINER_RUNTIME=colima mdify document.pdf
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Supported values:** `docker`, `podman`, `orbstack`, `colima`, `container`
|
|
190
|
+
|
|
191
|
+
If the selected runtime is installed but not running, mdify will display a helpful warning:
|
|
192
|
+
```
|
|
193
|
+
Warning: Found container runtime(s) but daemon is not running:
|
|
194
|
+
- orbstack (/opt/homebrew/bin/orbstack)
|
|
195
|
+
|
|
196
|
+
Please start one of these tools before running mdify.
|
|
197
|
+
macOS tip: Start OrbStack, Colima, or Podman Desktop application
|
|
198
|
+
```
|
|
152
199
|
|
|
153
200
|
With `--flat`, all output files are placed directly in the output directory. Directory paths are incorporated into filenames to prevent collisions:
|
|
154
201
|
|
|
@@ -11,7 +11,10 @@ A lightweight CLI for converting documents to Markdown. The CLI is fast to insta
|
|
|
11
11
|
## Requirements
|
|
12
12
|
|
|
13
13
|
- **Python 3.8+**
|
|
14
|
-
- **Docker** or
|
|
14
|
+
- **Docker**, **Podman**, or native macOS container tools (for document conversion)
|
|
15
|
+
- On macOS: Supports Apple Container (macOS 26+), OrbStack, Colima, Podman, or Docker Desktop
|
|
16
|
+
- On Linux: Docker or Podman
|
|
17
|
+
- Auto-detects available tools
|
|
15
18
|
|
|
16
19
|
## Installation
|
|
17
20
|
|
|
@@ -25,6 +28,13 @@ pipx install mdify-cli
|
|
|
25
28
|
|
|
26
29
|
Restart your terminal after installation.
|
|
27
30
|
|
|
31
|
+
For containerized document conversion, install one of these (or use Docker Desktop):
|
|
32
|
+
- **Apple Container** (macOS 26+): Download from https://github.com/apple/container/releases
|
|
33
|
+
- **OrbStack** (recommended): `brew install orbstack`
|
|
34
|
+
- **Colima**: `brew install colima && colima start`
|
|
35
|
+
- **Podman**: `brew install podman && podman machine init && podman machine start`
|
|
36
|
+
- **Docker Desktop**: Available at https://www.docker.com/products/docker-desktop
|
|
37
|
+
|
|
28
38
|
### Linux
|
|
29
39
|
|
|
30
40
|
```bash
|
|
@@ -111,13 +121,50 @@ The first conversion takes longer (~30-60s) as the container loads ML models int
|
|
|
111
121
|
| `-m, --mask` | ⚠️ **Deprecated**: PII masking not supported in current version |
|
|
112
122
|
| `--gpu` | Use GPU-accelerated container (requires NVIDIA GPU and nvidia-container-toolkit) |
|
|
113
123
|
| `--port PORT` | Container port (default: 5001) |
|
|
114
|
-
| `--runtime RUNTIME` | Container runtime: docker or
|
|
124
|
+
| `--runtime RUNTIME` | Container runtime: docker, podman, orbstack, colima, or container (auto-detected) |
|
|
115
125
|
| `--image IMAGE` | Custom container image (default: ghcr.io/docling-project/docling-serve-cpu:main) |
|
|
116
126
|
| `--pull POLICY` | Image pull policy: always, missing, never (default: missing) |
|
|
117
127
|
| `--check-update` | Check for available updates and exit |
|
|
118
128
|
| `--version` | Show version and exit |
|
|
119
129
|
|
|
120
|
-
###
|
|
130
|
+
### Container Runtime Selection
|
|
131
|
+
|
|
132
|
+
mdify automatically detects and uses the best available container runtime. The detection order differs by platform:
|
|
133
|
+
|
|
134
|
+
**macOS (recommended):**
|
|
135
|
+
1. Apple Container (native, macOS 26+ required)
|
|
136
|
+
2. OrbStack (lightweight, fast)
|
|
137
|
+
3. Colima (open-source alternative)
|
|
138
|
+
4. Podman (via Podman machine)
|
|
139
|
+
5. Docker Desktop (full Docker)
|
|
140
|
+
|
|
141
|
+
**Linux:**
|
|
142
|
+
1. Docker
|
|
143
|
+
2. Podman
|
|
144
|
+
|
|
145
|
+
**Override runtime:**
|
|
146
|
+
Use the `MDIFY_CONTAINER_RUNTIME` environment variable to force a specific runtime:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
export MDIFY_CONTAINER_RUNTIME=orbstack
|
|
150
|
+
mdify document.pdf
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Or inline:
|
|
154
|
+
```bash
|
|
155
|
+
MDIFY_CONTAINER_RUNTIME=colima mdify document.pdf
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Supported values:** `docker`, `podman`, `orbstack`, `colima`, `container`
|
|
159
|
+
|
|
160
|
+
If the selected runtime is installed but not running, mdify will display a helpful warning:
|
|
161
|
+
```
|
|
162
|
+
Warning: Found container runtime(s) but daemon is not running:
|
|
163
|
+
- orbstack (/opt/homebrew/bin/orbstack)
|
|
164
|
+
|
|
165
|
+
Please start one of these tools before running mdify.
|
|
166
|
+
macOS tip: Start OrbStack, Colima, or Podman Desktop application
|
|
167
|
+
```
|
|
121
168
|
|
|
122
169
|
With `--flat`, all output files are placed directly in the output directory. Directory paths are incorporated into filenames to prevent collisions:
|
|
123
170
|
|
|
@@ -10,6 +10,7 @@ is lightweight and has no ML dependencies.
|
|
|
10
10
|
import argparse
|
|
11
11
|
import json
|
|
12
12
|
import os
|
|
13
|
+
import platform
|
|
13
14
|
import shutil
|
|
14
15
|
import subprocess
|
|
15
16
|
import sys
|
|
@@ -33,7 +34,9 @@ CHECK_INTERVAL_SECONDS = 86400 # 24 hours
|
|
|
33
34
|
# Container configuration
|
|
34
35
|
DEFAULT_IMAGE = "ghcr.io/docling-project/docling-serve-cpu:main"
|
|
35
36
|
GPU_IMAGE = "ghcr.io/docling-project/docling-serve-cu126:main"
|
|
36
|
-
SUPPORTED_RUNTIMES = ("docker", "podman")
|
|
37
|
+
SUPPORTED_RUNTIMES = ("docker", "podman", "orbstack", "colima", "container")
|
|
38
|
+
MACOS_RUNTIMES_PRIORITY = ("container", "orbstack", "colima", "podman", "docker")
|
|
39
|
+
OTHER_RUNTIMES_PRIORITY = ("docker", "podman")
|
|
37
40
|
|
|
38
41
|
|
|
39
42
|
# =============================================================================
|
|
@@ -151,34 +154,117 @@ def check_for_update(force: bool = False) -> None:
|
|
|
151
154
|
# =============================================================================
|
|
152
155
|
|
|
153
156
|
|
|
154
|
-
def
|
|
157
|
+
def is_daemon_running(runtime: str) -> bool:
|
|
158
|
+
"""
|
|
159
|
+
Check if a container runtime daemon is running.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
runtime: Path to container runtime executable
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
True if daemon is running and responsive, False otherwise.
|
|
166
|
+
"""
|
|
167
|
+
try:
|
|
168
|
+
runtime_name = os.path.basename(runtime)
|
|
169
|
+
|
|
170
|
+
# Apple Container uses 'container system status' to check daemon
|
|
171
|
+
if runtime_name == "container":
|
|
172
|
+
result = subprocess.run(
|
|
173
|
+
[runtime, "system", "status"],
|
|
174
|
+
capture_output=True,
|
|
175
|
+
timeout=5,
|
|
176
|
+
check=False,
|
|
177
|
+
)
|
|
178
|
+
return result.returncode == 0
|
|
179
|
+
|
|
180
|
+
# Other runtimes use --version check
|
|
181
|
+
result = subprocess.run(
|
|
182
|
+
[runtime, "--version"],
|
|
183
|
+
capture_output=True,
|
|
184
|
+
timeout=5,
|
|
185
|
+
check=False,
|
|
186
|
+
)
|
|
187
|
+
return result.returncode == 0
|
|
188
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def detect_runtime(preferred: Optional[str] = None, explicit: bool = True) -> Optional[str]:
|
|
155
193
|
"""
|
|
156
194
|
Detect available container runtime.
|
|
157
195
|
|
|
196
|
+
First checks MDIFY_CONTAINER_RUNTIME environment variable for explicit override.
|
|
197
|
+
On macOS, tries native tools first (OrbStack → Colima → Podman → Docker).
|
|
198
|
+
On other platforms, tries Docker → Podman.
|
|
199
|
+
|
|
158
200
|
Args:
|
|
159
|
-
preferred: Preferred runtime (
|
|
160
|
-
explicit: If True,
|
|
161
|
-
If False, silently use alternative without warning.
|
|
162
|
-
Note: This only controls warning emission; selection order
|
|
163
|
-
is always preferred → alternative regardless of this flag.
|
|
201
|
+
preferred: Preferred runtime name override (deprecated, use MDIFY_CONTAINER_RUNTIME)
|
|
202
|
+
explicit: If True, print info about detection/fallback choices.
|
|
164
203
|
|
|
165
204
|
Returns:
|
|
166
205
|
Path to runtime executable, or None if not found.
|
|
167
206
|
"""
|
|
168
|
-
#
|
|
169
|
-
|
|
170
|
-
if
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
207
|
+
# Check for explicit environment variable override
|
|
208
|
+
env_runtime = os.environ.get("MDIFY_CONTAINER_RUNTIME", "").strip().lower()
|
|
209
|
+
if env_runtime:
|
|
210
|
+
if env_runtime not in SUPPORTED_RUNTIMES:
|
|
211
|
+
print(
|
|
212
|
+
f"Warning: MDIFY_CONTAINER_RUNTIME='{env_runtime}' is not supported. "
|
|
213
|
+
f"Supported: {', '.join(SUPPORTED_RUNTIMES)}",
|
|
214
|
+
file=sys.stderr,
|
|
215
|
+
)
|
|
216
|
+
else:
|
|
217
|
+
runtime_path = shutil.which(env_runtime)
|
|
218
|
+
if runtime_path:
|
|
219
|
+
if explicit:
|
|
220
|
+
print(f"Using runtime from MDIFY_CONTAINER_RUNTIME: {env_runtime}")
|
|
221
|
+
return runtime_path
|
|
222
|
+
else:
|
|
223
|
+
print(
|
|
224
|
+
f"Warning: MDIFY_CONTAINER_RUNTIME='{env_runtime}' specified but not found in PATH",
|
|
225
|
+
file=sys.stderr,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Determine runtime priority based on OS
|
|
229
|
+
is_macos = platform.system() == "Darwin"
|
|
230
|
+
if is_macos:
|
|
231
|
+
runtime_priority = MACOS_RUNTIMES_PRIORITY
|
|
177
232
|
if explicit:
|
|
233
|
+
print(f"Detected macOS: checking for native container tools...")
|
|
234
|
+
else:
|
|
235
|
+
runtime_priority = OTHER_RUNTIMES_PRIORITY
|
|
236
|
+
|
|
237
|
+
# Try each runtime in priority order
|
|
238
|
+
found_but_not_running = []
|
|
239
|
+
for runtime_name in runtime_priority:
|
|
240
|
+
runtime_path = shutil.which(runtime_name)
|
|
241
|
+
if runtime_path:
|
|
242
|
+
# Check if daemon is running
|
|
243
|
+
if is_daemon_running(runtime_path):
|
|
244
|
+
if explicit:
|
|
245
|
+
print(f"Using container runtime: {runtime_name}")
|
|
246
|
+
return runtime_path
|
|
247
|
+
else:
|
|
248
|
+
found_but_not_running.append((runtime_name, runtime_path))
|
|
249
|
+
|
|
250
|
+
# If we found tools but none are running, warn and ask user to start one
|
|
251
|
+
if found_but_not_running:
|
|
252
|
+
print(
|
|
253
|
+
f"\nWarning: Found container runtime(s) but daemon is not running:",
|
|
254
|
+
file=sys.stderr,
|
|
255
|
+
)
|
|
256
|
+
for runtime_name, runtime_path in found_but_not_running:
|
|
257
|
+
print(f" - {runtime_name} ({runtime_path})", file=sys.stderr)
|
|
258
|
+
print(
|
|
259
|
+
"\nPlease start one of these tools before running mdify.",
|
|
260
|
+
file=sys.stderr,
|
|
261
|
+
)
|
|
262
|
+
if is_macos:
|
|
178
263
|
print(
|
|
179
|
-
|
|
264
|
+
" macOS tip: Start OrbStack, Colima, or Podman Desktop application",
|
|
265
|
+
file=sys.stderr,
|
|
180
266
|
)
|
|
181
|
-
return
|
|
267
|
+
return None
|
|
182
268
|
|
|
183
269
|
return None
|
|
184
270
|
|
|
@@ -195,6 +281,27 @@ def check_image_exists(runtime: str, image: str) -> bool:
|
|
|
195
281
|
True if image exists locally.
|
|
196
282
|
"""
|
|
197
283
|
try:
|
|
284
|
+
runtime_name = os.path.basename(runtime)
|
|
285
|
+
|
|
286
|
+
# Apple Container uses 'image-list' command
|
|
287
|
+
if runtime_name == "container":
|
|
288
|
+
result = subprocess.run(
|
|
289
|
+
[runtime, "image-list", "--format", "json"],
|
|
290
|
+
capture_output=True,
|
|
291
|
+
check=False,
|
|
292
|
+
)
|
|
293
|
+
if result.returncode == 0 and result.stdout:
|
|
294
|
+
try:
|
|
295
|
+
images = json.loads(result.stdout.decode())
|
|
296
|
+
# Check if image exists in the list
|
|
297
|
+
for img in images:
|
|
298
|
+
if img.get("name") == image or image in img.get("repoTags", []):
|
|
299
|
+
return True
|
|
300
|
+
except json.JSONDecodeError:
|
|
301
|
+
pass
|
|
302
|
+
return False
|
|
303
|
+
|
|
304
|
+
# Docker/Podman/OrbStack/Colima use standard 'image inspect'
|
|
198
305
|
result = subprocess.run(
|
|
199
306
|
[runtime, "image", "inspect", image],
|
|
200
307
|
capture_output=True,
|
|
@@ -221,6 +328,18 @@ def pull_image(runtime: str, image: str, quiet: bool = False) -> bool:
|
|
|
221
328
|
print(f"Pulling image: {image}")
|
|
222
329
|
|
|
223
330
|
try:
|
|
331
|
+
runtime_name = os.path.basename(runtime)
|
|
332
|
+
|
|
333
|
+
# Apple Container uses 'image-pull' command
|
|
334
|
+
if runtime_name == "container":
|
|
335
|
+
result = subprocess.run(
|
|
336
|
+
[runtime, "image-pull", image],
|
|
337
|
+
capture_output=quiet,
|
|
338
|
+
check=False,
|
|
339
|
+
)
|
|
340
|
+
return result.returncode == 0
|
|
341
|
+
|
|
342
|
+
# Docker/Podman/OrbStack/Colima use standard 'pull'
|
|
224
343
|
result = subprocess.run(
|
|
225
344
|
[runtime, "pull", image],
|
|
226
345
|
capture_output=quiet,
|
|
@@ -302,7 +421,7 @@ def get_free_space(path: str) -> int:
|
|
|
302
421
|
|
|
303
422
|
def get_storage_root(runtime: str) -> Optional[str]:
|
|
304
423
|
"""
|
|
305
|
-
Get the storage root directory for Docker or
|
|
424
|
+
Get the storage root directory for Docker, Podman, OrbStack, or Colima.
|
|
306
425
|
|
|
307
426
|
Args:
|
|
308
427
|
runtime: Path to container runtime executable
|
|
@@ -331,6 +450,18 @@ def get_storage_root(runtime: str) -> Optional[str]:
|
|
|
331
450
|
if result.stdout:
|
|
332
451
|
info = json.loads(result.stdout.decode())
|
|
333
452
|
return info.get("store", {}).get("graphRoot")
|
|
453
|
+
elif runtime_name == "orbstack":
|
|
454
|
+
# OrbStack stores containers in ~/.orbstack
|
|
455
|
+
home = os.path.expanduser("~")
|
|
456
|
+
return os.path.join(home, ".orbstack")
|
|
457
|
+
elif runtime_name == "colima":
|
|
458
|
+
# Colima stores containers in ~/.colima
|
|
459
|
+
home = os.path.expanduser("~")
|
|
460
|
+
return os.path.join(home, ".colima")
|
|
461
|
+
elif runtime_name == "container":
|
|
462
|
+
# Apple Container stores data in Application Support
|
|
463
|
+
home = os.path.expanduser("~")
|
|
464
|
+
return os.path.join(home, "Library", "Application Support", "com.apple.container")
|
|
334
465
|
return None
|
|
335
466
|
except (OSError, json.JSONDecodeError):
|
|
336
467
|
return None
|
|
@@ -660,15 +791,14 @@ def main() -> int:
|
|
|
660
791
|
return 1
|
|
661
792
|
|
|
662
793
|
# Detect container runtime
|
|
663
|
-
|
|
794
|
+
# If --runtime is specified, treat as explicit user choice
|
|
664
795
|
explicit = args.runtime is not None
|
|
665
|
-
runtime = detect_runtime(preferred, explicit=explicit)
|
|
796
|
+
runtime = detect_runtime(preferred=args.runtime, explicit=explicit)
|
|
666
797
|
if runtime is None:
|
|
667
798
|
print(
|
|
668
799
|
f"Error: Container runtime not found ({', '.join(SUPPORTED_RUNTIMES)})",
|
|
669
800
|
file=sys.stderr,
|
|
670
801
|
)
|
|
671
|
-
print("Please install Docker or Podman to use mdify.", file=sys.stderr)
|
|
672
802
|
return 2
|
|
673
803
|
|
|
674
804
|
# Handle image pull policy
|
|
@@ -752,7 +882,8 @@ def main() -> int:
|
|
|
752
882
|
return 1
|
|
753
883
|
elif args.pull == "never" and not image_exists:
|
|
754
884
|
print(f"Error: Image not found locally: {image}", file=sys.stderr)
|
|
755
|
-
|
|
885
|
+
runtime_name = os.path.basename(runtime)
|
|
886
|
+
print(f"Run with --pull=missing or pull manually: {runtime_name} pull {image}")
|
|
756
887
|
return 1
|
|
757
888
|
|
|
758
889
|
# Resolve paths (use absolute() as fallback if resolve() fails due to permissions)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mdify-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.9.1
|
|
4
4
|
Summary: Convert PDFs and document images into structured Markdown for LLM workflows
|
|
5
5
|
Author: tiroq
|
|
6
6
|
License-Expression: MIT
|
|
@@ -42,7 +42,10 @@ A lightweight CLI for converting documents to Markdown. The CLI is fast to insta
|
|
|
42
42
|
## Requirements
|
|
43
43
|
|
|
44
44
|
- **Python 3.8+**
|
|
45
|
-
- **Docker** or
|
|
45
|
+
- **Docker**, **Podman**, or native macOS container tools (for document conversion)
|
|
46
|
+
- On macOS: Supports Apple Container (macOS 26+), OrbStack, Colima, Podman, or Docker Desktop
|
|
47
|
+
- On Linux: Docker or Podman
|
|
48
|
+
- Auto-detects available tools
|
|
46
49
|
|
|
47
50
|
## Installation
|
|
48
51
|
|
|
@@ -56,6 +59,13 @@ pipx install mdify-cli
|
|
|
56
59
|
|
|
57
60
|
Restart your terminal after installation.
|
|
58
61
|
|
|
62
|
+
For containerized document conversion, install one of these (or use Docker Desktop):
|
|
63
|
+
- **Apple Container** (macOS 26+): Download from https://github.com/apple/container/releases
|
|
64
|
+
- **OrbStack** (recommended): `brew install orbstack`
|
|
65
|
+
- **Colima**: `brew install colima && colima start`
|
|
66
|
+
- **Podman**: `brew install podman && podman machine init && podman machine start`
|
|
67
|
+
- **Docker Desktop**: Available at https://www.docker.com/products/docker-desktop
|
|
68
|
+
|
|
59
69
|
### Linux
|
|
60
70
|
|
|
61
71
|
```bash
|
|
@@ -142,13 +152,50 @@ The first conversion takes longer (~30-60s) as the container loads ML models int
|
|
|
142
152
|
| `-m, --mask` | ⚠️ **Deprecated**: PII masking not supported in current version |
|
|
143
153
|
| `--gpu` | Use GPU-accelerated container (requires NVIDIA GPU and nvidia-container-toolkit) |
|
|
144
154
|
| `--port PORT` | Container port (default: 5001) |
|
|
145
|
-
| `--runtime RUNTIME` | Container runtime: docker or
|
|
155
|
+
| `--runtime RUNTIME` | Container runtime: docker, podman, orbstack, colima, or container (auto-detected) |
|
|
146
156
|
| `--image IMAGE` | Custom container image (default: ghcr.io/docling-project/docling-serve-cpu:main) |
|
|
147
157
|
| `--pull POLICY` | Image pull policy: always, missing, never (default: missing) |
|
|
148
158
|
| `--check-update` | Check for available updates and exit |
|
|
149
159
|
| `--version` | Show version and exit |
|
|
150
160
|
|
|
151
|
-
###
|
|
161
|
+
### Container Runtime Selection
|
|
162
|
+
|
|
163
|
+
mdify automatically detects and uses the best available container runtime. The detection order differs by platform:
|
|
164
|
+
|
|
165
|
+
**macOS (recommended):**
|
|
166
|
+
1. Apple Container (native, macOS 26+ required)
|
|
167
|
+
2. OrbStack (lightweight, fast)
|
|
168
|
+
3. Colima (open-source alternative)
|
|
169
|
+
4. Podman (via Podman machine)
|
|
170
|
+
5. Docker Desktop (full Docker)
|
|
171
|
+
|
|
172
|
+
**Linux:**
|
|
173
|
+
1. Docker
|
|
174
|
+
2. Podman
|
|
175
|
+
|
|
176
|
+
**Override runtime:**
|
|
177
|
+
Use the `MDIFY_CONTAINER_RUNTIME` environment variable to force a specific runtime:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
export MDIFY_CONTAINER_RUNTIME=orbstack
|
|
181
|
+
mdify document.pdf
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Or inline:
|
|
185
|
+
```bash
|
|
186
|
+
MDIFY_CONTAINER_RUNTIME=colima mdify document.pdf
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Supported values:** `docker`, `podman`, `orbstack`, `colima`, `container`
|
|
190
|
+
|
|
191
|
+
If the selected runtime is installed but not running, mdify will display a helpful warning:
|
|
192
|
+
```
|
|
193
|
+
Warning: Found container runtime(s) but daemon is not running:
|
|
194
|
+
- orbstack (/opt/homebrew/bin/orbstack)
|
|
195
|
+
|
|
196
|
+
Please start one of these tools before running mdify.
|
|
197
|
+
macOS tip: Start OrbStack, Colima, or Podman Desktop application
|
|
198
|
+
```
|
|
152
199
|
|
|
153
200
|
With `--flat`, all output files are placed directly in the output directory. Directory paths are incorporated into filenames to prevent collisions:
|
|
154
201
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Tests for mdify CLI runtime detection."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import subprocess
|
|
4
5
|
import sys
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from unittest.mock import patch, Mock
|
|
@@ -9,6 +10,7 @@ from urllib.error import URLError
|
|
|
9
10
|
|
|
10
11
|
from mdify.cli import (
|
|
11
12
|
detect_runtime,
|
|
13
|
+
is_daemon_running,
|
|
12
14
|
parse_args,
|
|
13
15
|
format_size,
|
|
14
16
|
format_duration,
|
|
@@ -45,44 +47,47 @@ class TestDetectRuntime:
|
|
|
45
47
|
|
|
46
48
|
def test_auto_docker_exists(self):
|
|
47
49
|
with patch("mdify.cli.shutil.which") as mock_which:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
with patch("mdify.cli.is_daemon_running", return_value=True):
|
|
51
|
+
mock_which.side_effect = (
|
|
52
|
+
lambda x: "/usr/bin/docker" if x == "docker" else None
|
|
53
|
+
)
|
|
54
|
+
result = detect_runtime(explicit=False)
|
|
55
|
+
assert result == "/usr/bin/docker"
|
|
53
56
|
|
|
54
57
|
def test_auto_only_podman_exists(self, capsys):
|
|
55
58
|
with patch("mdify.cli.shutil.which") as mock_which:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
with patch("mdify.cli.is_daemon_running", return_value=True):
|
|
60
|
+
mock_which.side_effect = (
|
|
61
|
+
lambda x: "/usr/bin/podman" if x == "podman" else None
|
|
62
|
+
)
|
|
63
|
+
result = detect_runtime(explicit=False)
|
|
64
|
+
assert result == "/usr/bin/podman"
|
|
65
|
+
captured = capsys.readouterr()
|
|
66
|
+
assert captured.err == ""
|
|
63
67
|
|
|
64
68
|
def test_auto_neither_exists(self):
|
|
65
69
|
with patch("mdify.cli.shutil.which", return_value=None):
|
|
66
|
-
result = detect_runtime(
|
|
70
|
+
result = detect_runtime(explicit=False)
|
|
67
71
|
assert result is None
|
|
68
72
|
|
|
69
73
|
def test_explicit_docker_exists(self):
|
|
70
74
|
with patch("mdify.cli.shutil.which") as mock_which:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
with patch("mdify.cli.is_daemon_running", return_value=True):
|
|
76
|
+
mock_which.side_effect = (
|
|
77
|
+
lambda x: "/usr/bin/docker" if x == "docker" else None
|
|
78
|
+
)
|
|
79
|
+
result = detect_runtime("docker", explicit=True)
|
|
80
|
+
assert result == "/usr/bin/docker"
|
|
76
81
|
|
|
77
82
|
def test_explicit_docker_fallback_to_podman(self, capsys):
|
|
78
83
|
with patch("mdify.cli.shutil.which") as mock_which:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
with patch("mdify.cli.is_daemon_running", return_value=True):
|
|
85
|
+
mock_which.side_effect = (
|
|
86
|
+
lambda x: "/usr/bin/podman" if x == "podman" else None
|
|
87
|
+
)
|
|
88
|
+
result = detect_runtime("docker", explicit=True)
|
|
89
|
+
assert result == "/usr/bin/podman"
|
|
90
|
+
# With new macOS priority-based detection, priority order is used
|
|
86
91
|
|
|
87
92
|
def test_explicit_docker_neither_exists(self):
|
|
88
93
|
with patch("mdify.cli.shutil.which", return_value=None):
|
|
@@ -91,27 +96,166 @@ class TestDetectRuntime:
|
|
|
91
96
|
|
|
92
97
|
def test_explicit_podman_exists(self):
|
|
93
98
|
with patch("mdify.cli.shutil.which") as mock_which:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
with patch("mdify.cli.is_daemon_running", return_value=True):
|
|
100
|
+
mock_which.side_effect = (
|
|
101
|
+
lambda x: "/usr/bin/podman" if x == "podman" else None
|
|
102
|
+
)
|
|
103
|
+
result = detect_runtime("podman", explicit=True)
|
|
104
|
+
assert result == "/usr/bin/podman"
|
|
99
105
|
|
|
100
106
|
def test_explicit_podman_fallback_to_docker(self, capsys):
|
|
101
107
|
with patch("mdify.cli.shutil.which") as mock_which:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
with patch("mdify.cli.is_daemon_running", return_value=True):
|
|
109
|
+
mock_which.side_effect = (
|
|
110
|
+
lambda x: "/usr/bin/docker" if x == "docker" else None
|
|
111
|
+
)
|
|
112
|
+
result = detect_runtime("podman", explicit=True)
|
|
113
|
+
assert result == "/usr/bin/docker"
|
|
114
|
+
# With new macOS priority-based detection, priority order is used
|
|
109
115
|
|
|
110
116
|
def test_explicit_podman_neither_exists(self):
|
|
111
117
|
with patch("mdify.cli.shutil.which", return_value=None):
|
|
112
118
|
result = detect_runtime("podman", explicit=True)
|
|
113
119
|
assert result is None
|
|
114
120
|
|
|
121
|
+
# Tests for new macOS native tool support
|
|
122
|
+
def test_env_var_override_orbstack(self, monkeypatch):
|
|
123
|
+
"""Test MDIFY_CONTAINER_RUNTIME env var overrides detection."""
|
|
124
|
+
monkeypatch.setenv("MDIFY_CONTAINER_RUNTIME", "orbstack")
|
|
125
|
+
with patch("mdify.cli.shutil.which") as mock_which:
|
|
126
|
+
mock_which.return_value = "/usr/local/bin/orbstack"
|
|
127
|
+
result = detect_runtime(explicit=False)
|
|
128
|
+
assert result == "/usr/local/bin/orbstack"
|
|
129
|
+
|
|
130
|
+
def test_env_var_override_colima(self, monkeypatch):
|
|
131
|
+
"""Test MDIFY_CONTAINER_RUNTIME env var works for colima."""
|
|
132
|
+
monkeypatch.setenv("MDIFY_CONTAINER_RUNTIME", "colima")
|
|
133
|
+
with patch("mdify.cli.shutil.which") as mock_which:
|
|
134
|
+
mock_which.return_value = "/usr/local/bin/colima"
|
|
135
|
+
result = detect_runtime(explicit=False)
|
|
136
|
+
assert result == "/usr/local/bin/colima"
|
|
137
|
+
|
|
138
|
+
def test_env_var_not_found_in_path(self, monkeypatch, capsys):
|
|
139
|
+
"""Test MDIFY_CONTAINER_RUNTIME env var when tool not in PATH."""
|
|
140
|
+
monkeypatch.setenv("MDIFY_CONTAINER_RUNTIME", "orbstack")
|
|
141
|
+
with patch("mdify.cli.shutil.which", return_value=None):
|
|
142
|
+
result = detect_runtime(explicit=False)
|
|
143
|
+
assert result is None
|
|
144
|
+
captured = capsys.readouterr()
|
|
145
|
+
assert "MDIFY_CONTAINER_RUNTIME='orbstack' specified but not found in PATH" in captured.err
|
|
146
|
+
|
|
147
|
+
def test_env_var_invalid_name(self, monkeypatch, capsys):
|
|
148
|
+
"""Test MDIFY_CONTAINER_RUNTIME with invalid runtime name."""
|
|
149
|
+
monkeypatch.setenv("MDIFY_CONTAINER_RUNTIME", "invalid")
|
|
150
|
+
with patch("mdify.cli.shutil.which", return_value=None):
|
|
151
|
+
result = detect_runtime(explicit=False)
|
|
152
|
+
assert result is None
|
|
153
|
+
captured = capsys.readouterr()
|
|
154
|
+
assert "MDIFY_CONTAINER_RUNTIME='invalid' is not supported" in captured.err
|
|
155
|
+
|
|
156
|
+
def test_macos_priority_orbstack_first(self, monkeypatch):
|
|
157
|
+
"""Test macOS prefers OrbStack over other tools."""
|
|
158
|
+
monkeypatch.setenv("MDIFY_CONTAINER_RUNTIME", "") # Clear env override
|
|
159
|
+
with patch("mdify.cli.platform.system", return_value="Darwin"):
|
|
160
|
+
with patch("mdify.cli.shutil.which") as mock_which:
|
|
161
|
+
with patch("mdify.cli.is_daemon_running") as mock_running:
|
|
162
|
+
# Setup: orbstack exists and running
|
|
163
|
+
mock_which.side_effect = lambda x: f"/usr/local/bin/{x}" if x == "orbstack" else None
|
|
164
|
+
mock_running.return_value = True
|
|
165
|
+
result = detect_runtime(explicit=False)
|
|
166
|
+
assert result == "/usr/local/bin/orbstack"
|
|
167
|
+
|
|
168
|
+
def test_macos_fallback_colima_when_orbstack_not_running(self, monkeypatch):
|
|
169
|
+
"""Test macOS falls back to Colima if OrbStack not running."""
|
|
170
|
+
monkeypatch.setenv("MDIFY_CONTAINER_RUNTIME", "")
|
|
171
|
+
with patch("mdify.cli.platform.system", return_value="Darwin"):
|
|
172
|
+
with patch("mdify.cli.shutil.which") as mock_which:
|
|
173
|
+
with patch("mdify.cli.is_daemon_running") as mock_running:
|
|
174
|
+
# Setup: orbstack exists but not running, colima exists and running
|
|
175
|
+
def which_side_effect(x):
|
|
176
|
+
if x in ("orbstack", "colima"):
|
|
177
|
+
return f"/usr/local/bin/{x}"
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
def running_side_effect(path):
|
|
181
|
+
return "colima" in path
|
|
182
|
+
|
|
183
|
+
mock_which.side_effect = which_side_effect
|
|
184
|
+
mock_running.side_effect = running_side_effect
|
|
185
|
+
result = detect_runtime(explicit=False)
|
|
186
|
+
assert result == "/usr/local/bin/colima"
|
|
187
|
+
|
|
188
|
+
def test_non_macos_priority_docker_first(self, monkeypatch):
|
|
189
|
+
"""Test non-macOS prefers Docker over other tools."""
|
|
190
|
+
monkeypatch.setenv("MDIFY_CONTAINER_RUNTIME", "")
|
|
191
|
+
with patch("mdify.cli.platform.system", return_value="Linux"):
|
|
192
|
+
with patch("mdify.cli.shutil.which") as mock_which:
|
|
193
|
+
with patch("mdify.cli.is_daemon_running") as mock_running:
|
|
194
|
+
# Setup: docker and podman exist, docker running
|
|
195
|
+
def which_side_effect(x):
|
|
196
|
+
if x in ("docker", "podman"):
|
|
197
|
+
return f"/usr/bin/{x}"
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
def running_side_effect(path):
|
|
201
|
+
return "docker" in path
|
|
202
|
+
|
|
203
|
+
mock_which.side_effect = which_side_effect
|
|
204
|
+
mock_running.side_effect = running_side_effect
|
|
205
|
+
result = detect_runtime(explicit=False)
|
|
206
|
+
assert result == "/usr/bin/docker"
|
|
207
|
+
|
|
208
|
+
def test_macos_priority_apple_container_first(self, monkeypatch):
|
|
209
|
+
"""Test macOS prefers Apple Container over other tools."""
|
|
210
|
+
monkeypatch.setenv("MDIFY_CONTAINER_RUNTIME", "")
|
|
211
|
+
with patch("mdify.cli.platform.system", return_value="Darwin"):
|
|
212
|
+
with patch("mdify.cli.shutil.which") as mock_which:
|
|
213
|
+
with patch("mdify.cli.is_daemon_running") as mock_running:
|
|
214
|
+
# Setup: container exists and running
|
|
215
|
+
mock_which.side_effect = lambda x: f"/usr/local/bin/{x}" if x == "container" else None
|
|
216
|
+
mock_running.return_value = True
|
|
217
|
+
result = detect_runtime(explicit=False)
|
|
218
|
+
assert result == "/usr/local/bin/container"
|
|
219
|
+
|
|
220
|
+
def test_macos_fallback_orbstack_when_container_not_running(self, monkeypatch):
|
|
221
|
+
"""Test macOS falls back to OrbStack if Apple Container not running."""
|
|
222
|
+
monkeypatch.setenv("MDIFY_CONTAINER_RUNTIME", "")
|
|
223
|
+
with patch("mdify.cli.platform.system", return_value="Darwin"):
|
|
224
|
+
with patch("mdify.cli.shutil.which") as mock_which:
|
|
225
|
+
with patch("mdify.cli.is_daemon_running") as mock_running:
|
|
226
|
+
# Setup: container exists but not running, orbstack exists and running
|
|
227
|
+
def which_side_effect(x):
|
|
228
|
+
if x in ("container", "orbstack"):
|
|
229
|
+
return f"/usr/local/bin/{x}"
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
def running_side_effect(path):
|
|
233
|
+
return "orbstack" in path
|
|
234
|
+
|
|
235
|
+
mock_which.side_effect = which_side_effect
|
|
236
|
+
mock_running.side_effect = running_side_effect
|
|
237
|
+
result = detect_runtime(explicit=False)
|
|
238
|
+
assert result == "/usr/local/bin/orbstack"
|
|
239
|
+
|
|
240
|
+
def test_all_tools_exist_but_not_running(self, monkeypatch, capsys):
|
|
241
|
+
"""Test warning when tools exist but none are running."""
|
|
242
|
+
monkeypatch.setenv("MDIFY_CONTAINER_RUNTIME", "")
|
|
243
|
+
with patch("mdify.cli.platform.system", return_value="Darwin"):
|
|
244
|
+
with patch("mdify.cli.shutil.which") as mock_which:
|
|
245
|
+
with patch("mdify.cli.is_daemon_running", return_value=False):
|
|
246
|
+
def which_side_effect(x):
|
|
247
|
+
if x in ("orbstack", "colima", "podman", "docker"):
|
|
248
|
+
return f"/usr/local/bin/{x}"
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
mock_which.side_effect = which_side_effect
|
|
252
|
+
result = detect_runtime(explicit=False)
|
|
253
|
+
assert result is None
|
|
254
|
+
captured = capsys.readouterr()
|
|
255
|
+
assert "daemon is not running" in captured.err
|
|
256
|
+
assert "orbstack" in captured.err
|
|
257
|
+
assert "colima" in captured.err
|
|
258
|
+
|
|
115
259
|
|
|
116
260
|
class TestNewCLIArgs:
|
|
117
261
|
"""Test new CLI arguments for docling-serve."""
|
|
@@ -673,6 +817,66 @@ class TestFileHandling:
|
|
|
673
817
|
assert result == output_dir / "doc.md"
|
|
674
818
|
|
|
675
819
|
|
|
820
|
+
class TestIsDaemonRunning:
|
|
821
|
+
"""Tests for is_daemon_running() function."""
|
|
822
|
+
|
|
823
|
+
def test_daemon_running_returns_true(self):
|
|
824
|
+
"""Test is_daemon_running returns True when daemon is responsive."""
|
|
825
|
+
mock_result = Mock()
|
|
826
|
+
mock_result.returncode = 0
|
|
827
|
+
with patch("mdify.cli.subprocess.run", return_value=mock_result):
|
|
828
|
+
result = is_daemon_running("/usr/bin/docker")
|
|
829
|
+
assert result is True
|
|
830
|
+
|
|
831
|
+
def test_daemon_not_running_returns_false(self):
|
|
832
|
+
"""Test is_daemon_running returns False when daemon is not responsive."""
|
|
833
|
+
mock_result = Mock()
|
|
834
|
+
mock_result.returncode = 1
|
|
835
|
+
with patch("mdify.cli.subprocess.run", return_value=mock_result):
|
|
836
|
+
result = is_daemon_running("/usr/bin/docker")
|
|
837
|
+
assert result is False
|
|
838
|
+
|
|
839
|
+
def test_daemon_timeout_returns_false(self):
|
|
840
|
+
"""Test is_daemon_running returns False on timeout."""
|
|
841
|
+
with patch("mdify.cli.subprocess.run", side_effect=subprocess.TimeoutExpired("cmd", 5)):
|
|
842
|
+
result = is_daemon_running("/usr/bin/docker")
|
|
843
|
+
assert result is False
|
|
844
|
+
|
|
845
|
+
def test_daemon_oserror_returns_false(self):
|
|
846
|
+
"""Test is_daemon_running returns False on OSError."""
|
|
847
|
+
with patch("mdify.cli.subprocess.run", side_effect=OSError("No such file")):
|
|
848
|
+
result = is_daemon_running("/usr/bin/nonexistent")
|
|
849
|
+
assert result is False
|
|
850
|
+
|
|
851
|
+
def test_apple_container_daemon_running(self):
|
|
852
|
+
"""Test is_daemon_running uses 'system status' for Apple Container."""
|
|
853
|
+
mock_result = Mock()
|
|
854
|
+
mock_result.returncode = 0
|
|
855
|
+
with patch("mdify.cli.subprocess.run", return_value=mock_result) as mock_run:
|
|
856
|
+
result = is_daemon_running("/usr/local/bin/container")
|
|
857
|
+
assert result is True
|
|
858
|
+
mock_run.assert_called_once_with(
|
|
859
|
+
["/usr/local/bin/container", "system", "status"],
|
|
860
|
+
capture_output=True,
|
|
861
|
+
timeout=5,
|
|
862
|
+
check=False,
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
def test_apple_container_daemon_not_running(self):
|
|
866
|
+
"""Test is_daemon_running returns False when Apple Container daemon not running."""
|
|
867
|
+
mock_result = Mock()
|
|
868
|
+
mock_result.returncode = 1
|
|
869
|
+
with patch("mdify.cli.subprocess.run", return_value=mock_result) as mock_run:
|
|
870
|
+
result = is_daemon_running("/usr/local/bin/container")
|
|
871
|
+
assert result is False
|
|
872
|
+
mock_run.assert_called_once_with(
|
|
873
|
+
["/usr/local/bin/container", "system", "status"],
|
|
874
|
+
capture_output=True,
|
|
875
|
+
timeout=5,
|
|
876
|
+
check=False,
|
|
877
|
+
)
|
|
878
|
+
|
|
879
|
+
|
|
676
880
|
class TestContainerRuntime:
|
|
677
881
|
"""Tests for container runtime functions."""
|
|
678
882
|
|
|
@@ -774,6 +978,47 @@ class TestContainerRuntime:
|
|
|
774
978
|
captured = capsys.readouterr()
|
|
775
979
|
assert "Error pulling image" in captured.err
|
|
776
980
|
|
|
981
|
+
def test_apple_container_pull_success(self):
|
|
982
|
+
"""Test pull_image uses 'image-pull' for Apple Container."""
|
|
983
|
+
mock_result = Mock()
|
|
984
|
+
mock_result.returncode = 0
|
|
985
|
+
with patch("mdify.cli.subprocess.run", return_value=mock_result) as mock_run:
|
|
986
|
+
result = pull_image("/usr/local/bin/container", "test-image", quiet=True)
|
|
987
|
+
assert result is True
|
|
988
|
+
mock_run.assert_called_once_with(
|
|
989
|
+
["/usr/local/bin/container", "image-pull", "test-image"],
|
|
990
|
+
capture_output=True,
|
|
991
|
+
check=False,
|
|
992
|
+
)
|
|
993
|
+
|
|
994
|
+
def test_apple_container_image_exists(self):
|
|
995
|
+
"""Test check_image_exists uses 'image-list' for Apple Container."""
|
|
996
|
+
mock_result = Mock()
|
|
997
|
+
mock_result.returncode = 0
|
|
998
|
+
mock_result.stdout = json.dumps([
|
|
999
|
+
{"name": "test-image", "repoTags": ["test-image:latest"]},
|
|
1000
|
+
{"name": "other-image", "repoTags": ["other-image:latest"]}
|
|
1001
|
+
]).encode()
|
|
1002
|
+
with patch("mdify.cli.subprocess.run", return_value=mock_result) as mock_run:
|
|
1003
|
+
result = check_image_exists("/usr/local/bin/container", "test-image")
|
|
1004
|
+
assert result is True
|
|
1005
|
+
mock_run.assert_called_once_with(
|
|
1006
|
+
["/usr/local/bin/container", "image-list", "--format", "json"],
|
|
1007
|
+
capture_output=True,
|
|
1008
|
+
check=False,
|
|
1009
|
+
)
|
|
1010
|
+
|
|
1011
|
+
def test_apple_container_image_not_exists(self):
|
|
1012
|
+
"""Test check_image_exists returns False when image not in list."""
|
|
1013
|
+
mock_result = Mock()
|
|
1014
|
+
mock_result.returncode = 0
|
|
1015
|
+
mock_result.stdout = json.dumps([
|
|
1016
|
+
{"name": "other-image", "repoTags": ["other-image:latest"]}
|
|
1017
|
+
]).encode()
|
|
1018
|
+
with patch("mdify.cli.subprocess.run", return_value=mock_result):
|
|
1019
|
+
result = check_image_exists("/usr/local/bin/container", "test-image")
|
|
1020
|
+
assert result is False
|
|
1021
|
+
|
|
777
1022
|
|
|
778
1023
|
class TestGetStorageRoot:
|
|
779
1024
|
"""Tests for get_storage_root() function."""
|
|
@@ -874,6 +1119,60 @@ class TestGetStorageRoot:
|
|
|
874
1119
|
result = get_storage_root("podman")
|
|
875
1120
|
assert result is None
|
|
876
1121
|
|
|
1122
|
+
def test_orbstack_storage_root(self, monkeypatch):
|
|
1123
|
+
"""Test get_storage_root returns OrbStack storage root."""
|
|
1124
|
+
from mdify.cli import get_storage_root
|
|
1125
|
+
|
|
1126
|
+
home = "/Users/testuser"
|
|
1127
|
+
monkeypatch.setenv("HOME", home)
|
|
1128
|
+
result = get_storage_root("/usr/local/bin/orbstack")
|
|
1129
|
+
assert result == f"{home}/.orbstack"
|
|
1130
|
+
|
|
1131
|
+
def test_colima_storage_root(self, monkeypatch):
|
|
1132
|
+
"""Test get_storage_root returns Colima storage root."""
|
|
1133
|
+
from mdify.cli import get_storage_root
|
|
1134
|
+
|
|
1135
|
+
home = "/Users/testuser"
|
|
1136
|
+
monkeypatch.setenv("HOME", home)
|
|
1137
|
+
result = get_storage_root("/usr/local/bin/colima")
|
|
1138
|
+
assert result == f"{home}/.colima"
|
|
1139
|
+
|
|
1140
|
+
def test_orbstack_storage_root_with_full_path(self, monkeypatch):
|
|
1141
|
+
"""Test get_storage_root works with full path to OrbStack executable."""
|
|
1142
|
+
from mdify.cli import get_storage_root
|
|
1143
|
+
|
|
1144
|
+
home = "/Users/apple"
|
|
1145
|
+
monkeypatch.setenv("HOME", home)
|
|
1146
|
+
result = get_storage_root("/opt/homebrew/bin/orbstack")
|
|
1147
|
+
assert result == f"{home}/.orbstack"
|
|
1148
|
+
|
|
1149
|
+
def test_colima_storage_root_with_full_path(self, monkeypatch):
|
|
1150
|
+
"""Test get_storage_root works with full path to Colima executable."""
|
|
1151
|
+
from mdify.cli import get_storage_root
|
|
1152
|
+
|
|
1153
|
+
home = "/Users/apple"
|
|
1154
|
+
monkeypatch.setenv("HOME", home)
|
|
1155
|
+
result = get_storage_root("/opt/homebrew/bin/colima")
|
|
1156
|
+
assert result == f"{home}/.colima"
|
|
1157
|
+
|
|
1158
|
+
def test_apple_container_storage_root(self, monkeypatch):
|
|
1159
|
+
"""Test get_storage_root returns Apple Container storage root."""
|
|
1160
|
+
from mdify.cli import get_storage_root
|
|
1161
|
+
|
|
1162
|
+
home = "/Users/testuser"
|
|
1163
|
+
monkeypatch.setenv("HOME", home)
|
|
1164
|
+
result = get_storage_root("/usr/local/bin/container")
|
|
1165
|
+
assert result == f"{home}/Library/Application Support/com.apple.container"
|
|
1166
|
+
|
|
1167
|
+
def test_apple_container_storage_root_with_full_path(self, monkeypatch):
|
|
1168
|
+
"""Test get_storage_root works with full path to Apple Container executable."""
|
|
1169
|
+
from mdify.cli import get_storage_root
|
|
1170
|
+
|
|
1171
|
+
home = "/Users/apple"
|
|
1172
|
+
monkeypatch.setenv("HOME", home)
|
|
1173
|
+
result = get_storage_root("/opt/homebrew/bin/container")
|
|
1174
|
+
assert result == f"{home}/Library/Application Support/com.apple.container"
|
|
1175
|
+
|
|
877
1176
|
|
|
878
1177
|
class TestGetImageSizeEstimate:
|
|
879
1178
|
"""Tests for get_image_size_estimate function."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|