From 0aa51c6e51542a5c19f6a5258df022141244966f Mon Sep 17 00:00:00 2001 From: Marius Vollmer Date: Tue, 10 Sep 2024 12:35:54 +0300 Subject: [PATCH] lib: Add path functions --- files.js | 2 ++ pkg/lib/path.ts | 46 ++++++++++++++++++++++++++ pkg/lib/test-path.ts | 57 ++++++++++++++++++++++++++++++++ pkg/sosreport/sosreport.jsx | 5 +-- pkg/storaged/btrfs/subvolume.jsx | 12 ++----- 5 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 pkg/lib/path.ts create mode 100644 pkg/lib/test-path.ts diff --git a/files.js b/files.js index 87e64c62428..d839c5252c9 100644 --- a/files.js +++ b/files.js @@ -78,6 +78,8 @@ const info = { "base1/test-user.js", "base1/test-websocket.js", + "lib/test-path.ts", + "kdump/test-config-client.js", "networkmanager/test-utils.js", diff --git a/pkg/lib/path.ts b/pkg/lib/path.ts new file mode 100644 index 00000000000..6f7ab35ef53 --- /dev/null +++ b/pkg/lib/path.ts @@ -0,0 +1,46 @@ +/* + * This file is part of Cockpit. + * + * Copyright (C) 2024 Red Hat, Inc. + * + * Cockpit is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Cockpit is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Cockpit; If not, see . + */ + +function drop_slashes(path : string): string { + // Drop all trailing slashes, but never drop the first character. + let pos = path.length; + while (pos > 1 && path[pos - 1] == "/") + pos -= 1; + return pos == path.length ? path : path.substr(0, pos); +} + +export function dirname(path : string): string { + const norm = drop_slashes(path); + const pos = norm.lastIndexOf("/"); + if (pos < 0) + return "."; + else if (pos == 0) + return "/"; + else + return drop_slashes(norm.substr(0, pos)); +} + +export function basename(path : string): string { + const norm = drop_slashes(path); + const pos = norm.lastIndexOf("/"); + if (pos < 0) + return norm; + else + return norm.substr(pos + 1); +} diff --git a/pkg/lib/test-path.ts b/pkg/lib/test-path.ts new file mode 100644 index 00000000000..afc6e0d72db --- /dev/null +++ b/pkg/lib/test-path.ts @@ -0,0 +1,57 @@ +/* + * This file is part of Cockpit. + * + * Copyright (C) 2024 Red Hat, Inc. + * + * Cockpit is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Cockpit is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Cockpit; If not, see . + */ + +import { dirname, basename } from "./path"; +import QUnit from "qunit-tests"; + +QUnit.test("dirname", function (assert) { + const checks = [ + ["foo", "."], + ["/", "/"], + ["foo/bar", "foo"], + ["/foo", "/"], + ["foo///", "."], + ["/foo///", "/"], + ["////", "/"], + ["//foo///", "/"], + ["///foo///bar///", "///foo"], + ]; + + assert.expect(checks.length); + for (let i = 0; i < checks.length; i++) { + assert.strictEqual(dirname(checks[i][0]), checks[i][1], + "dirname(" + checks[i][0] + ") = " + checks[i][1]); + } +}); + +QUnit.test("basename", function (assert) { + const checks = [ + ["foo", "foo"], + ["bar/foo/", "foo"], + ["//bar//foo///", "foo"], + ]; + + assert.expect(checks.length); + for (let i = 0; i < checks.length; i++) { + assert.strictEqual(basename(checks[i][0]), checks[i][1], + "basename(" + checks[i][0] + ") = " + checks[i][1]); + } +}); + +QUnit.start(); diff --git a/pkg/sosreport/sosreport.jsx b/pkg/sosreport/sosreport.jsx index 64fb050f7cc..044e7051357 100644 --- a/pkg/sosreport/sosreport.jsx +++ b/pkg/sosreport/sosreport.jsx @@ -40,6 +40,7 @@ import { EyeIcon, EyeSlashIcon } from '@patternfly/react-icons'; import { EmptyStatePanel } from "cockpit-components-empty-state.jsx"; import { ListingTable } from "cockpit-components-table.jsx"; +import { basename as path_basename } from "path"; import cockpit from "cockpit"; import { superuser } from "superuser"; @@ -74,7 +75,7 @@ function sosLister() { } function parse_report_name(path) { - const basename = path.replace(/.*\//, ""); + const basename = path_basename(path); const archive_rx = /^(secured-)?sosreport-(.*)\.tar\.[^.]+(\.gpg)?$/; const m = basename.match(archive_rx); if (m) { @@ -197,7 +198,7 @@ function sosCreate(args, setProgress, setError, setErrorDetail) { } function sosDownload(path) { - const basename = path.replace(/.*\//, ""); + const basename = path_basename(path); const query = window.btoa(JSON.stringify({ payload: "fsread1", binary: "raw", diff --git a/pkg/storaged/btrfs/subvolume.jsx b/pkg/storaged/btrfs/subvolume.jsx index 9fad2f92eff..b373732626b 100644 --- a/pkg/storaged/btrfs/subvolume.jsx +++ b/pkg/storaged/btrfs/subvolume.jsx @@ -24,6 +24,8 @@ import { Button } from "@patternfly/react-core/dist/esm/components/Button/index. import { Card, CardBody, CardHeader, CardTitle } from "@patternfly/react-core/dist/esm/components/Card/index.js"; import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; +import { dirname } from "path"; + import { PageTable, StorageCard, StorageDescription, ChildrenTable, new_card, new_page, navigate_away_from_card, register_crossref, get_crossrefs, @@ -249,14 +251,6 @@ function subvolume_delete(volume, subvol, mount_point_in_parent, card) { }); } -function dirname(path) { - const i = path.lastIndexOf("/"); - if (i < 0) - return null; - else - return path.substr(0, i); -} - export function make_btrfs_subvolume_pages(parent, volume) { let subvols = client.uuids_btrfs_subvols[volume.data.uuid]; if (!subvols) { @@ -297,7 +291,7 @@ export function make_btrfs_subvolume_pages(parent, volume) { let dn = pn; while (true) { dn = dirname(dn); - if (!dn) { + if (dn == "." || dn == "/") { subvols_by_pathname[pn].parent = 5; break; } else if (subvols_by_pathname[dn]) {