Coverage for /home/benjarobin/Bootlin/projects/Schneider-Electric-Senux/sbom-cve-check/src/sbom_cve_check/utils/parsing.py: 53%
62 statements
« prev ^ index » next coverage.py v7.11.1, created at 2025-11-28 15:37 +0100
« prev ^ index » next coverage.py v7.11.1, created at 2025-11-28 15:37 +0100
1# -*- coding: utf-8 -*-
2# SPDX-License-Identifier: GPL-2.0-only
4from collections.abc import Callable
5from configparser import ConfigParser
6from datetime import UTC, datetime, timedelta
7from typing import Any, TypeVar
9__DURATION_MULTIPLIER = {
10 "seconds": 1.0,
11 "second": 1.0,
12 "sec": 1.0,
13 "s": 1.0,
14 "minutes": 60.0,
15 "minute": 60.0,
16 "min": 60.0,
17 "months": 2629800.0,
18 "month": 2629800.0,
19 "M": 2629800.0,
20 "msec": 1.0e-3,
21 "ms": 1.0e-3,
22 "m": 60.0,
23 "hours": 3600.0,
24 "hour": 3600.0,
25 "hr": 3600.0,
26 "h": 3600.0,
27 "days": 86400.0,
28 "day": 86400.0,
29 "d": 86400.0,
30 "weeks": 604800.0,
31 "week": 604800.0,
32 "w": 604800.0,
33}
35_ParamT = TypeVar("_ParamT")
38def parse_duration(v: object) -> timedelta:
39 unit = "s"
40 if isinstance(v, (int, float)):
41 val = float(v)
42 elif isinstance(v, str):
43 idx_unit = next(
44 (i for i, ch in enumerate(v) if not (ch.isdigit() or ch == ".")), -1
45 )
46 if idx_unit == 0:
47 raise ValueError(f"Invalid duration: {v}")
49 if idx_unit < 0:
50 val = float(v.strip())
51 else:
52 val = float(v[:idx_unit].strip())
53 unit = v[idx_unit:].strip()
54 else:
55 raise TypeError(f"Invalid duration type: {v}")
57 multiplier = __DURATION_MULTIPLIER.get(unit)
58 if not multiplier:
59 raise ValueError(f"Invalid duration suffix: {v}")
61 return timedelta(seconds=val * multiplier)
64def parse_boolean(v: object) -> bool:
65 """Parse the given value as a boolean. Currently, supports integers or strings."""
66 b = None
67 if isinstance(v, str):
68 b = ConfigParser.BOOLEAN_STATES.get(v.lower())
69 elif isinstance(v, int) and (v in {0, 1}):
70 b = bool(v)
72 if b is None:
73 raise ValueError(f"Invalid boolean value: {v}")
74 return b
77def parse_list(v: object) -> list[str]:
78 if not isinstance(v, str):
79 raise TypeError(f"Cannot parse list, not a string: {v}")
80 return [v.strip() for v in v.split(",")]
83def _update_param_value(
84 args: dict[str, Any],
85 cfg_name: str,
86 new_name: str | None,
87 required: bool,
88 param_type: type[_ParamT],
89 parse_fn: Callable[[Any], _ParamT | None],
90) -> None:
91 """
92 Low-level helper to update the parameter `cfg_name` in `args`,
93 optionally under `new_name`.
94 If the value in `args`'s `cfg_name` is not of type `param_type`, use
95 `parse_fn` to convert it.
96 """
97 v = args.pop(cfg_name, None)
98 if v is None:
99 if required:
100 raise ValueError(f"Missing {cfg_name} {param_type.__name__} parameter")
101 return
103 args[new_name or cfg_name] = v if isinstance(v, param_type) else parse_fn(v)
106def update_boolean_param(
107 args: dict[str, Any],
108 cfg_name: str,
109 new_name: str | None = None,
110 required: bool = False,
111) -> None:
112 _update_param_value(args, cfg_name, new_name, required, bool, parse_boolean)
115def update_integer_param(
116 args: dict[str, Any],
117 cfg_name: str,
118 new_name: str | None = None,
119 required: bool = False,
120) -> None:
121 _update_param_value(args, cfg_name, new_name, required, int, int)
124def update_timedelta_param(
125 args: dict[str, Any],
126 cfg_name: str,
127 special_vals: dict[str, timedelta | None],
128 new_name: str | None = None,
129 required: bool = False,
130) -> None:
131 def _parse_duration(s: str) -> timedelta | None:
132 if s in special_vals:
133 return special_vals[s]
134 return parse_duration(s)
136 _update_param_value(args, cfg_name, new_name, required, timedelta, _parse_duration)
139def update_list_param(
140 args: dict[str, Any],
141 cfg_name: str,
142 new_name: str | None = None,
143 required: bool = False,
144) -> None:
145 _update_param_value(args, cfg_name, new_name, required, list, parse_list)
148def datetime_from_iso_format(s: str | None) -> datetime | None:
149 if not s:
150 return None
152 d = datetime.fromisoformat(s)
153 if not d.tzinfo:
154 return d.replace(tzinfo=UTC, microsecond=0)
155 return d.replace(microsecond=0)