mirror of
https://gitlab.com/khumba/nvd.git
synced 2024-11-27 07:03:49 +01:00
Highlight the changed parts of version numbers (issue #17).
This commit is contained in:
parent
1276c2d3eb
commit
30ecb231b7
2 changed files with 106 additions and 10 deletions
|
@ -2,6 +2,11 @@
|
|||
|
||||
## 0.2.4 (unreleased)
|
||||
|
||||
- In the "Version changes" section of a diff, highlight the portions of version
|
||||
numbers that have changed, i.e., everything after the part common to all
|
||||
versions (issue #17). This makes it easier to spot whether packages have had
|
||||
major or minor version bumps.
|
||||
|
||||
- Respect the `NO_COLOR` environment variable and disable colour when it is set
|
||||
and nonempty (when the default `--color=auto` is used). For more info see:
|
||||
https://no-color.org
|
||||
|
|
109
src/nvd
109
src/nvd
|
@ -68,6 +68,7 @@ SGR_YELLOW = 3
|
|||
SGR_BLUE = 4
|
||||
SGR_MAGENTA = 5
|
||||
SGR_CYAN = 6
|
||||
SGR_WHITE = 7
|
||||
|
||||
def sgr(*args):
|
||||
assert all(isinstance(arg, int) for arg in args), \
|
||||
|
@ -97,6 +98,8 @@ SEL_NO_MANIFESTS = ""
|
|||
|
||||
NIX_STORE_PATH_REGEX = re.compile(r"^/nix/store/[a-z0-9]+-(.+?)(-([0-9].*?))?(\.drv)?$")
|
||||
|
||||
ALPHANUM_RE = re.compile(r"\w") # Alternatively [0-9a-zA-Z] would work.
|
||||
|
||||
def raise_arg(e):
|
||||
raise e
|
||||
|
||||
|
@ -312,7 +315,7 @@ class PackageManifestPair:
|
|||
self._right_manifest.contains_pname(pname)
|
||||
return in_left_manifest != in_right_manifest
|
||||
|
||||
def parse_pname_version(path: str) -> Tuple[str, str]:
|
||||
def parse_pname_version(path: str) -> Tuple[str, Optional[str]]:
|
||||
base_path = str(StorePath(path).to_base_path().path())
|
||||
|
||||
match = NIX_STORE_PATH_REGEX.search(base_path)
|
||||
|
@ -323,8 +326,8 @@ def parse_pname_version(path: str) -> Tuple[str, str]:
|
|||
|
||||
return pname, version
|
||||
|
||||
def closure_paths_to_map(paths: List[str]) -> Dict[str, List[str]]:
|
||||
result = {}
|
||||
def closure_paths_to_map(paths: List[str]) -> Dict[str, List[Optional[str]]]:
|
||||
result: Dict[str, List[Optional[str]]] = {}
|
||||
|
||||
for path in paths:
|
||||
name, version = parse_pname_version(path)
|
||||
|
@ -338,7 +341,12 @@ def closure_paths_to_map(paths: List[str]) -> Dict[str, List[str]]:
|
|||
|
||||
return result
|
||||
|
||||
def render_versions(version_list: List[str], *, colour: bool = False) -> str:
|
||||
def render_versions(
|
||||
version_list: List[Optional[str]],
|
||||
*,
|
||||
colour: bool = False,
|
||||
highlight_from_pos: Optional[int] = None,
|
||||
) -> str:
|
||||
if version_list == []:
|
||||
return "<no versions>"
|
||||
|
||||
|
@ -355,7 +363,15 @@ def render_versions(version_list: List[str], *, colour: bool = False) -> str:
|
|||
if version is None:
|
||||
version = "<none>"
|
||||
elif colour: # (Don't apply colour to <none>.)
|
||||
if highlight_from_pos is None:
|
||||
version = sgr(SGR_FG + SGR_YELLOW) + version + sgr(SGR_RESET)
|
||||
else:
|
||||
version = \
|
||||
sgr(SGR_FG + SGR_YELLOW) \
|
||||
+ version[0:highlight_from_pos] \
|
||||
+ sgr(SGR_BOLD) \
|
||||
+ version[highlight_from_pos:] \
|
||||
+ sgr(SGR_RESET)
|
||||
|
||||
if count == 1:
|
||||
items.append(version)
|
||||
|
@ -372,6 +388,7 @@ def print_package_list(
|
|||
left_versions_map: Dict[str, List[str]],
|
||||
right_versions_map: Optional[Dict[str, List[str]]] = None,
|
||||
fixed_install_state: Optional[str] = None,
|
||||
no_version_suffix_highlight: bool = False,
|
||||
):
|
||||
assert (right_versions_map is None) != (fixed_install_state is None), \
|
||||
"Expect either one versions map, or a fixed install state."
|
||||
|
@ -418,6 +435,15 @@ def print_package_list(
|
|||
bold_if_selected_sgr = ""
|
||||
pname_sgr = bold_if_selected_sgr + sgr(SGR_FG + SGR_GREEN)
|
||||
|
||||
versions_common_prefix_len: Optional[int]
|
||||
if have_single_version or no_version_suffix_highlight:
|
||||
versions_common_prefix_len = None
|
||||
else:
|
||||
versions_common_prefix_len = find_common_version_prefix_lists(
|
||||
[x for x in left_versions if x is not None],
|
||||
[x for x in right_versions if x is not None],
|
||||
)
|
||||
|
||||
status_str = "[{}{}]".format(
|
||||
bold_if_selected_sgr + install_state_str + sgr(SGR_RESET),
|
||||
pname_sgr + manifest_pair.get_selection_state(pname) + sgr(SGR_RESET),
|
||||
|
@ -428,11 +454,66 @@ def print_package_list(
|
|||
status_str,
|
||||
count_str,
|
||||
pname_sgr + pname_str + sgr(SGR_RESET),
|
||||
render_versions(left_versions, colour=True),
|
||||
"" if have_single_version else (" -> " + render_versions(right_versions, colour=True)),
|
||||
render_versions(
|
||||
left_versions,
|
||||
colour=True,
|
||||
highlight_from_pos=versions_common_prefix_len,
|
||||
),
|
||||
"" if have_single_version else (
|
||||
" -> " + render_versions(
|
||||
right_versions,
|
||||
colour=True,
|
||||
highlight_from_pos=versions_common_prefix_len,
|
||||
)
|
||||
),
|
||||
))
|
||||
count += 1
|
||||
|
||||
def find_common_version_prefix(x: str, y: str, limit: Optional[int]) -> int:
|
||||
i = 0
|
||||
x_len = len(x)
|
||||
y_len = len(y)
|
||||
|
||||
while (
|
||||
(limit is None or i < limit)
|
||||
and i < x_len
|
||||
and i < y_len
|
||||
and x[i] == y[i]
|
||||
):
|
||||
i += 1
|
||||
|
||||
return i
|
||||
|
||||
def find_common_version_prefix_lists(*lsts: List[str]) -> int:
|
||||
first = True
|
||||
i: int = 0
|
||||
target: str
|
||||
|
||||
for lst in lsts:
|
||||
for x in lst:
|
||||
if first:
|
||||
target = x
|
||||
i = len(x)
|
||||
first = False
|
||||
else:
|
||||
i = find_common_version_prefix(target, x, i)
|
||||
|
||||
if i == 0:
|
||||
return 0
|
||||
|
||||
# Rewind to the start of the current alphanumeric span of characters. It
|
||||
# looks weird to show e.g. "1.15 -> 1.17" with only the 5 and 7 highlighted.
|
||||
# It's nicer to highlight 15 and 17.
|
||||
#
|
||||
# It might also make sense to rewind from the middle of a number, or from
|
||||
# the middle of a word, but *not* cross over from a word to a number or vice
|
||||
# versa.
|
||||
if not first and i < len(target) and i > 0 and ALPHANUM_RE.fullmatch(target[i]):
|
||||
while i > 0 and ALPHANUM_RE.fullmatch(target[i - 1]):
|
||||
i -= 1
|
||||
|
||||
return i
|
||||
|
||||
def query_closure_disk_usage_bytes(target: Path) -> Optional[int]:
|
||||
# If we don't add "./" to relative paths, then newer nix will interpret the
|
||||
# argument as something related to flakes instead.
|
||||
|
@ -562,7 +643,12 @@ def run_list(*, root, only_selected, name_patterns):
|
|||
fixed_install_state=INST_INSTALLED,
|
||||
)
|
||||
|
||||
def run_diff(*, root1, root2):
|
||||
def run_diff(
|
||||
*,
|
||||
root1,
|
||||
root2,
|
||||
no_version_suffix_highlight: bool,
|
||||
):
|
||||
left_path = Path(root1)
|
||||
right_path = Path(root2)
|
||||
|
||||
|
@ -606,8 +692,8 @@ def run_diff(*, root1, root2):
|
|||
).stdout.rstrip("\n").split("\n")
|
||||
|
||||
# Maps from pname to lists of versions.
|
||||
left_closure_map: Dict[str, List[str]] = closure_paths_to_map(left_closure_paths)
|
||||
right_closure_map: Dict[str, List[str]] = closure_paths_to_map(right_closure_paths)
|
||||
left_closure_map: Dict[str, List[Optional[str]]] = closure_paths_to_map(left_closure_paths)
|
||||
right_closure_map: Dict[str, List[Optional[str]]] = closure_paths_to_map(right_closure_paths)
|
||||
|
||||
left_package_names = set(left_closure_map.keys())
|
||||
right_package_names = set(right_closure_map.keys())
|
||||
|
@ -636,6 +722,7 @@ def run_diff(*, root1, root2):
|
|||
manifest_pair=manifest_pair,
|
||||
left_versions_map=left_closure_map,
|
||||
right_versions_map=right_closure_map,
|
||||
no_version_suffix_highlight=no_version_suffix_highlight,
|
||||
)
|
||||
|
||||
# Announce specific changes for packages whose versions haven't changed.
|
||||
|
@ -724,6 +811,10 @@ def parse_args():
|
|||
diff_parser = subparsers.add_parser(
|
||||
"diff",
|
||||
help="Diff two Nix store paths and their closures.")
|
||||
diff_parser.add_argument(
|
||||
"--no-version-suffix-highlight",
|
||||
action="store_true",
|
||||
help="Disable highlighting of the changed portions of versions.")
|
||||
diff_parser.add_argument(
|
||||
"root1",
|
||||
help="The first Nix store path to compare.")
|
||||
|
|
Loading…
Reference in a new issue