Skip to content

Commit

Permalink
test: help commands
Browse files Browse the repository at this point in the history
  • Loading branch information
UnderKoen committed Oct 17, 2023
1 parent a0a953e commit e06f4c2
Show file tree
Hide file tree
Showing 5 changed files with 491 additions and 112 deletions.
6 changes: 3 additions & 3 deletions src/executor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TError, TFunction, TScript, TScripts } from "./types";
import child_process from "node:child_process";
import { isCI } from "ci-info";
import { printCommand, printCommands } from "./help";
import { Help } from "./help";

type Options = {
excludeArgs?: true;
Expand Down Expand Up @@ -54,13 +54,13 @@ class Executor {
}

if (Object.hasOwn(context, "$description")) {
printCommand(context, rest);
Help.printCommand(context, rest);
console.log();
}

console.log(`\x1b[1mTry one of the following:\x1b[0m`);

printCommands(context, rest, false);
Help.printCommands(context, rest, false);
} else {
console.error(`\x1b[31mScript '${path.join(".")}' not found\x1b[0m`);
}
Expand Down
211 changes: 108 additions & 103 deletions src/help.ts
Original file line number Diff line number Diff line change
@@ -1,131 +1,136 @@
import { TConfig, TScript } from "./types";
import minimist from "minimist";

export function printHelp(
config: TConfig,
argv: minimist.ParsedArgs,
args: unknown,
): void {
const helps: string[] = [];
if (typeof args === "boolean") {
if (!args) return;
} else if (typeof args === "string" || typeof args === "number") {
helps.push(`${args}`);
} else if (Array.isArray(args)) {
helps.push(...(args as string[]));
} else {
return;
}
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
class Help {
static printHelp(
config: TConfig,
argv: minimist.ParsedArgs,
args: unknown,
): void {
const helps: string[] = [];
if (typeof args === "boolean") {
if (!args) return;
} else if (typeof args === "string" || typeof args === "number") {
helps.push(`${args}`);
} else if (Array.isArray(args)) {
helps.push(...(args as string[]));
} else {
return;
}

helps.push(...argv._);
helps.push(...argv._);

if (helps.length === 0) {
//Printing all commands
console.log(`\n\x1b[1mAvailable commands:\x1b[0m`);
printCommands(config.scripts);
} else {
// TODO clean up this mess
for (const help of helps) {
console.log(`\n\x1b[1mAvailable commands: \x1b[90m${help}\x1b[0m`);
const path = help.split(".");
if (helps.length === 0) {
//Printing all commands
console.log(`\n\x1b[1mAvailable commands:\x1b[0m`);
Help.printCommands(config.scripts);
} else {
// TODO clean up this mess
for (const help of helps) {
console.log(`\n\x1b[1mAvailable commands: \x1b[90m${help}\x1b[0m`);
const path = help.split(".");

const todo: [TScript, string[], string[]][] = [
[config.scripts, [], path],
];
while (todo.length > 0) {
const current = todo.shift();
if (!current) continue;
const todo: [TScript, string[], string[]][] = [
[config.scripts, [], path],
];
while (todo.length > 0) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const current = todo.shift()!;

if (current[2].length === 0) {
printCommands(current[0], current[1]);
} else if (current[2][0] === "*") {
if (typeof current[0] === "object") {
if (Array.isArray(current[0])) {
for (let i = 0; i < current[0].length; i++) {
todo.push([current[0][i], [...current[1], i.toString()], []]);
}
} else {
for (const key in current[0]) {
if (Object.hasOwn(current[0], key)) {
todo.push([
current[0][key],
[...current[1], key],
current[2].slice(1),
]);
if (current[2].length === 0) {
Help.printCommands(current[0], current[1]);
} else if (current[2][0] === "*") {
if (typeof current[0] === "object") {
if (Array.isArray(current[0])) {
for (let i = 0; i < current[0].length; i++) {
todo.push([current[0][i], [...current[1], i.toString()], []]);
}
} else {
for (const key in current[0]) {
if (Object.hasOwn(current[0], key)) {
todo.push([
current[0][key],
[...current[1], key],
current[2].slice(1),
]);
}
}
}
}
}
} else {
const sub = current[2][0];
} else {
const sub = current[2][0];

if (typeof current[0] === "object") {
if (Array.isArray(current[0])) {
const i = parseInt(sub);
todo.push([
current[0][i],
[...current[1], i.toString()],
current[2].slice(1),
]);
} else {
todo.push([
current[0][sub],
[...current[1], sub],
current[2].slice(1),
]);
if (typeof current[0] === "object") {
if (Array.isArray(current[0])) {
const i = parseInt(sub);
todo.push([
current[0][i],
[...current[1], i.toString()],
current[2].slice(1),
]);
} else {
todo.push([
current[0][sub],
[...current[1], sub],
current[2].slice(1),
]);
}
}
}
}
}
}
process.exit(0);
}
process.exit(0);
}

export function printCommands(
script: TScript | undefined,
path: string[] = [],
printSelf = true,
) {
if (Array.isArray(script)) {
script.forEach((s, i) => {
printCommand(s, [...path, i.toString()]);
});
} else if (typeof script === "object") {
if (printSelf && path.length > 0) printCommand(script, [...path]);
for (const key in script) {
if (Object.hasOwn(script, key)) {
printCommand(script[key], [...path, key]);
static printCommands(
script: TScript | undefined,
path: string[] = [],
printSelf = true,
) {
if (Array.isArray(script)) {
script.forEach((s, i) => {
Help.printCommand(s, [...path, i.toString()]);
});
} else if (typeof script === "object") {
if (printSelf && path.length > 0) Help.printCommand(script, [...path]);
for (const key in script) {
if (Object.hasOwn(script, key)) {
Help.printCommand(script[key], [...path, key]);
}
}
} else {
Help.printCommand(script, path);
}
} else {
printCommand(script, path);
}
}

export function printCommand(scripts: TScript | undefined, path: string[]) {
if (scripts === undefined) {
console.error(`\x1b[31mScript '${path.join(".")}' not found\x1b[0m`);
return;
}
static printCommand(scripts: TScript | undefined, path: string[]) {
if (scripts === undefined) {
console.error(`\x1b[31mScript '${path.join(".")}' not found\x1b[0m`);
return;
}

if (path[path.length - 1] === "$description") return;
if (path[path.length - 1] === "$description") return;

const prefix = `\x1b[92m${path.join(".")}\x1b[90m - \x1b[0m`;
let suffix: string;
if (typeof scripts === "string") {
suffix = scripts;
} else if (typeof scripts === "function") {
suffix = "\x1b[90m<function>\x1b[0m";
} else if (Array.isArray(scripts)) {
suffix = `\x1b[90m[${scripts.length}]\x1b[0m`;
} else {
if (Object.hasOwn(scripts, "$description")) {
suffix = `\x1b[90m${scripts.$description as string}\x1b[0m`;
const prefix = `\x1b[92m${path.join(".")}\x1b[90m - \x1b[0m`;
let suffix: string;
if (typeof scripts === "string") {
suffix = scripts;
} else if (typeof scripts === "function") {
suffix = "\x1b[90m<function>\x1b[0m";
} else if (Array.isArray(scripts)) {
suffix = `\x1b[90m[${scripts.length}]\x1b[0m`;
} else {
suffix = `\x1b[90m{${Object.keys(scripts).join(", ")}}\x1b[0m`;
if (Object.hasOwn(scripts, "$description")) {
suffix = `\x1b[90m${scripts.$description as string}\x1b[0m`;
} else {
suffix = `\x1b[90m{${Object.keys(scripts).join(", ")}}\x1b[0m`;
}
}
}

console.log(`${prefix}${suffix}`);
console.log(`${prefix}${suffix}`);
}
}

export { Help };
8 changes: 4 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as fs from "node:fs";
import path from "path";

import { TScript, TError } from "./types";
import { printHelp } from "./help";
import { Help } from "./help";
import { loadConfig } from "./configLoader";
import { Executor } from "./executor";

Expand All @@ -17,8 +17,8 @@ process.argv = argv["--"] ?? [];
const config = loadConfig(argv);

async function main() {
printHelp(config, argv, argv.h);
printHelp(config, argv, argv.help);
Help.printHelp(config, argv, argv.h);
Help.printHelp(config, argv, argv.help);

handleNoArgs();

Expand Down Expand Up @@ -51,7 +51,7 @@ function handleNoArgs() {
return;
}

printHelp(config, argv, true);
Help.printHelp(config, argv, true);
}

function addToPath(env: NodeJS.ProcessEnv, path: string | null): void {
Expand Down
79 changes: 77 additions & 2 deletions test/executor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,21 @@ import { Executor } from "../src/executor";
import sinon from "sinon";
import child_process from "node:child_process";
import { TError } from "../src/types";
import { Help } from "../src/help";

sinon.restore();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let consoleLog = sinon.stub(console, "log");
let consoleError = sinon.stub(console, "error");

function suite(name: string): ReturnType<typeof _suite> {
const test = _suite(name);
test.before.each(() => {
sinon.restore();

// Don't output anything
sinon.stub(console, "log");
sinon.stub(console, "error");
consoleLog = sinon.stub(console, "log");
consoleError = sinon.stub(console, "error");
process.argv = [];
});
return test;
Expand Down Expand Up @@ -52,6 +58,75 @@ notFoundSuite("with ignoreNotFound option should not exit", () => {
assert.equal(exit.callCount, 0);
});

notFoundSuite("with no options should print error", () => {
// Arrange
sinon.stub(process, "exit");

// Act
Executor.notFound(["test"], {});

// Assert
assert.equal(consoleError.callCount, 1);
assert.match(consoleError.args[0][0] as string, "Script 'test' not found");
});

notFoundSuite("with context should print subscripts", () => {
// Arrange
sinon.stub(process, "exit");
const printCommand = sinon.stub(Help, "printCommand");
const printCommands = sinon.stub(Help, "printCommands");

// Act
Executor.notFound(["wow", "test"], {}, { r: "test" });

// Assert
assert.equal(consoleError.callCount, 1);
assert.match(
consoleError.args[0][0] as string,
"Script 'wow' does not have a 'test' script",
);
assert.equal(printCommand.callCount, 0);
assert.equal(printCommands.callCount, 1);
});

notFoundSuite("with context should print description", () => {
// Arrange
sinon.stub(process, "exit");
const printCommand = sinon.stub(Help, "printCommand");
const printCommands = sinon.stub(Help, "printCommands");

// Act
Executor.notFound(["wow", "test"], {}, { r: "test", $description: "desc" });

// Assert
assert.equal(consoleError.callCount, 1);
assert.match(
consoleError.args[0][0] as string,
"Script 'wow' does not have a 'test' script",
);
assert.equal(printCommand.callCount, 1);
assert.equal(printCommands.callCount, 1);
});

notFoundSuite("with context root should not say does not have", () => {
// Arrange
sinon.stub(process, "exit");
const printCommand = sinon.stub(Help, "printCommand");
const printCommands = sinon.stub(Help, "printCommands");

// Act
Executor.notFound(["test"], {}, { r: "test" });

// Assert
assert.equal(consoleError.callCount, 1);
assert.match(
consoleError.args[0][0] as string,
"Script 'test' does not exist",
);
assert.equal(printCommand.callCount, 0);
assert.equal(printCommands.callCount, 1);
});

notFoundSuite.run();
// endregion

Expand Down
Loading

0 comments on commit e06f4c2

Please sign in to comment.