Skip to content

Commit e9712e6

Browse files
authored
ls: Show keys and values (#42)
Previously we only showed values. However, these are often not requested. Also show the value that should be requested. Requires p0-security/app#1446 Screenshot: --- <img width="538" alt="image" src="https://github.com/p0-security/p0cli/assets/2490631/dd70ab7b-68da-4d8d-a1d7-0353ad2b15d3">
1 parent 94a2fce commit e9712e6

File tree

5 files changed

+42
-13
lines changed

5 files changed

+42
-13
lines changed

jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ module.exports = {
44
prettierPath: null,
55
preset: "ts-jest",
66
testEnvironment: "node",
7-
modulePathIgnorePatterns: ["/build"],
7+
modulePathIgnorePatterns: ["/dist"],
88
};

src/commands/__tests__/__snapshots__/ls.test.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ exports[`ls when valid ls command should print list response: stderr 1`] = `
3737
exports[`ls when valid ls command should print list response: stdout 1`] = `
3838
[
3939
[
40-
"instance-1",
40+
"instance-1[02m - Group / Resource 1[00m",
4141
],
4242
[
43-
"instance-2",
43+
"instance-2[02m - Resource 2[00m",
4444
],
4545
]
4646
`;

src/commands/__tests__/ls.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe("ls", () => {
2828
describe("when valid ls command", () => {
2929
const command = "ls ssh destination";
3030

31-
const mockItems = (items: string[]) =>
31+
const mockItems = (items: object[]) =>
3232
mockFetchCommand.mockResolvedValue({
3333
ok: true,
3434
term: "",
@@ -37,7 +37,10 @@ describe("ls", () => {
3737
});
3838

3939
it("should print list response", async () => {
40-
mockItems(["instance-1", "instance-2"]);
40+
mockItems([
41+
{ key: "instance-1", group: "Group", value: "Resource 1" },
42+
{ key: "instance-2", value: "Resource 2" },
43+
]);
4144
await lsCommand(yargs()).parse(command);
4245
expect(mockPrint1.mock.calls).toMatchSnapshot("stdout");
4346
expect(mockPrint2.mock.calls).toMatchSnapshot("stderr");

src/commands/ls.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ You should have received a copy of the GNU General Public License along with @p0
1111
import { fetchCommand } from "../drivers/api";
1212
import { authenticate } from "../drivers/auth";
1313
import { guard } from "../drivers/firestore";
14-
import { print2, print1 } from "../drivers/stdio";
14+
import { print2, print1, Ansi } from "../drivers/stdio";
15+
import { max } from "lodash";
1516
import pluralize from "pluralize";
1617
import yargs from "yargs";
1718

1819
type LsResponse = {
1920
ok: true;
20-
items: string[];
21+
items: { key: string; value: string; group?: string }[];
2122
isTruncated: boolean;
2223
term: string;
2324
arg: string;
@@ -50,20 +51,36 @@ const ls = async (
5051
"ls",
5152
...args.arguments,
5253
]);
54+
const allArguments = [...args._, ...args.arguments];
5355

5456
if (data && "ok" in data && data.ok) {
5557
const label = pluralize(data.arg);
5658
if (data.items.length === 0) {
5759
print2(`No ${label}`);
5860
return;
5961
}
60-
print2(
61-
`Showing${
62-
data.isTruncated ? ` the first ${data.items.length}` : ""
63-
} ${label}${data.term ? ` matching '${data.term}'` : ""}:`
64-
);
62+
const truncationPart = data.isTruncated
63+
? ` the first ${data.items.length}`
64+
: "";
65+
const postfixPart = data.term
66+
? ` matching '${data.term}'`
67+
: data.isTruncated
68+
? ` (use \`p0
69+
${allArguments.join(" ")} <like>\` to narrow results)`
70+
: "";
71+
72+
print2(`Showing${truncationPart} ${label}${postfixPart}:`);
73+
const isSameValue = data.items.every((i) => !i.group && i.key === i.value);
74+
const maxLength = max(data.items.map((i) => i.key.length)) || 0;
6575
for (const item of data.items) {
66-
print1(item);
76+
const tagPart = `${item.group ? `${item.group} / ` : ""}${item.value}`;
77+
print1(
78+
isSameValue
79+
? item.key
80+
: maxLength > 30
81+
? `${item.key}\n ${Ansi.Dim}${tagPart}${Ansi.Reset}`
82+
: `${item.key.padEnd(maxLength)}${Ansi.Dim} - ${tagPart}${Ansi.Reset}`
83+
);
6784
}
6885
} else {
6986
throw data;

src/drivers/stdio.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ This file is part of @p0security/p0cli
88
99
You should have received a copy of the GNU General Public License along with @p0security/p0cli. If not, see <https://www.gnu.org/licenses/>.
1010
**/
11+
1112
/** Functions to handle stdio
1213
*
1314
* These are essentially wrappers around console.foo, but allow for
1415
* - Better testing
1516
* - Later redirection / duplication
1617
*/
18+
import { mapValues } from "lodash";
1719

1820
/** Used to output machine-readable text to stdout
1921
*
@@ -33,3 +35,10 @@ export function print2(message: any) {
3335
// eslint-disable-next-line no-console
3436
console.error(message);
3537
}
38+
39+
const AnsiCodes = {
40+
Reset: "00",
41+
Dim: "02",
42+
} as const;
43+
44+
export const Ansi = mapValues(AnsiCodes, (v) => `\u001b[${v}m`);

0 commit comments

Comments
 (0)