• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nvidia-holoscan / holoscan-cli / 27541816701

15 Jun 2026 11:02AM UTC coverage: 77.129% (-0.1%) from 77.234%
27541816701

push

github

wyli
Add holoscan-cli tool-runner alias (#190)

Add a holoscan-cli console script alias so package-name based tool runners can execute the CLI directly while keeping holoscan as the canonical command. Extend wheel smoke coverage and CI tool-runner checks for uvx and pipx run.

Co-authored-by: Codex <noreply@openai.com>
(cherry picked from commit a0416b020)

2961 of 3839 relevant lines covered (77.13%)

0.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.01
/src/holoscan_cli/utils/text.py
1
#!/usr/bin/env python3
2
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3
# SPDX-License-Identifier: Apache-2.0
4
#
5
# Licensed under the Apache License, Version 2.0 (the "License");
6
# you may not use this file except in compliance with the License.
7
# You may obtain a copy of the License at
8
#
9
# http://www.apache.org/licenses/LICENSE-2.0
10
#
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS,
13
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
# See the License for the specific language governing permissions and
15
# limitations under the License.
16

17
"""Pure data helpers — text/version/distance, env/arg parsing, filesystem stats.
18

19
No subprocess and no terminal I/O — everything in this module is testable
20
without side effects beyond reading os.environ or the filesystem.
21
"""
22

23
import os
1✔
24
import re
1✔
25
import time
1✔
26
from pathlib import Path
1✔
27
from typing import List, Optional, Tuple
1✔
28

29
# ---- version parsing ---------------------------------------------------------
30

31

32
def parse_semantic_version(version: str) -> Tuple[int, int, int]:
1✔
33
    """
34
    Parse semantic version string MAJOR.MINOR.PATCH into tuple of integers for comparison
35

36
    Note: Implementing our own version parsing to avoid dependency on PyPI 'packaging' module.
37

38
    ref: https://semver.org/
39
    """
40
    match = re.match(r"^(\d+\.\d+\.\d+).*", version.strip())
1✔
41
    if not match:
1✔
42
        raise ValueError(f"Failed to parse semantic version string: {version}")
×
43
    return tuple(map(int, match.group(1).split(".")))
1✔
44

45

46
# ---- string helpers ----------------------------------------------------------
47

48

49
def _slugify(text: str, max_len: int = 63) -> str:
1✔
50
    """Make a branch slug: lowercase, non-alnum to '-', trim dashes, max length."""
51
    lowered = text.lower()
×
52
    replaced = re.sub(r"[^a-z0-9]+", "-", lowered)
×
53
    trimmed = replaced.strip("-")
×
54
    return trimmed[:max_len]
×
55

56

57
def levenshtein_distance(s1: str, s2: str) -> int:
1✔
58
    """Calculate the Levenshtein distance between two strings."""
59
    s1 = s1.lower()
1✔
60
    s2 = s2.lower()
1✔
61

62
    if len(s1) < len(s2):
1✔
63
        return levenshtein_distance(s2, s1)
×
64

65
    if len(s2) == 0:
1✔
66
        return len(s1)
×
67

68
    previous_row = range(len(s2) + 1)
1✔
69
    for i, c1 in enumerate(s1):
1✔
70
        current_row = [i + 1]
1✔
71
        for j, c2 in enumerate(s2):
1✔
72
            insertions = previous_row[j + 1] + 1
1✔
73
            deletions = current_row[j] + 1
1✔
74
            substitutions = previous_row[j] + (c1 != c2)
1✔
75
            current_row.append(min(insertions, deletions, substitutions))
1✔
76
        previous_row = current_row
1✔
77

78
    return previous_row[-1]
1✔
79

80

81
# ---- env / CLI arg helpers ---------------------------------------------------
82

83

84
def get_env_bool(
1✔
85
    env_var_name: str,
86
    default: bool = True,
87
    false_values: Tuple[str, ...] = ("false", "no", "n", "0", "f"),
88
) -> Tuple[str, bool]:
89
    """Check environment variable as boolean flag"""
90
    env_value = os.environ.get(env_var_name, str(default).lower())
1✔
91
    is_true = env_value.lower() not in false_values
1✔
92
    return env_value, is_true
1✔
93

94

95
def get_cli_arg_value(args: List[str], flag: str) -> Optional[str]:
1✔
96
    """Return the last value of ``flag`` in a CLI argument list.
97

98
    Supports both ``--flag value`` and ``--flag=value`` forms. Returns ``None``
99
    if the flag is not present. The last-wins rule mirrors typical CLI behavior
100
    where later occurrences override earlier ones.
101
    """
102
    value: Optional[str] = None
1✔
103
    prefix = f"{flag}="
1✔
104
    i = 0
1✔
105
    while i < len(args):
1✔
106
        arg = args[i]
1✔
107
        if arg == flag and i + 1 < len(args):
1✔
108
            value = args[i + 1]
1✔
109
            i += 2
1✔
110
            continue
1✔
111
        if arg.startswith(prefix):
1✔
112
            value = arg.removeprefix(prefix)
1✔
113
        i += 1
1✔
114
    return value
1✔
115

116

117
def normalize_args_str(args):
1✔
118
    """Convert arguments to string format, handling both string and array inputs"""
119
    if isinstance(args, str):
1✔
120
        return os.path.expandvars(args)
1✔
121
    elif isinstance(args, list):
1✔
122
        expanded_args = [os.path.expandvars(arg) for arg in args]
1✔
123
        return " ".join(expanded_args)
1✔
124
    return ""
×
125

126

127
# ---- filesystem stats + reporting --------------------------------------------
128

129

130
def dir_size_mb(path: Path) -> float:
1✔
131
    """Return the total size of a directory tree in megabytes."""
132
    total = 0
1✔
133
    for root, _dirs, files in os.walk(str(path)):
1✔
134
        for f in files:
1✔
135
            try:
1✔
136
                total += os.path.getsize(os.path.join(root, f))
1✔
137
            except OSError:
×
138
                continue
×
139
    return total / (1024 * 1024)
1✔
140

141

142
def relative_time(mtime: float) -> str:
1✔
143
    """Format an mtime as a human-readable relative time string."""
144
    elapsed = time.time() - mtime
1✔
145
    if elapsed < 60:
1✔
146
        return "just now"
1✔
147
    if elapsed < 3600:
×
148
        return f"{int(elapsed / 60)}m ago"
×
149
    if elapsed < 86400:
×
150
        return f"{int(elapsed / 3600)}h ago"
×
151
    return f"{int(elapsed / 86400)}d ago"
×
152

153

154
def format_size(mb: float) -> str:
1✔
155
    """Format a size in megabytes as a human-readable string."""
156
    if mb >= 1024:
1✔
157
        return f"{mb / 1024:.1f} GB"
1✔
158
    return f"{mb:.0f} MB"
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc