Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

verify options for subcommands #51

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/commands/android/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export const AVAILABLE_SUBCOMMANDS: AvailableSubcommands = {
options: [
{
name: 'wireless',
description: 'Connect a real device wirelessly'
description: 'Connect a real device wirelessly',
valuedOptions: []
}
]
}
Expand Down
25 changes: 18 additions & 7 deletions src/commands/android/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,25 @@ export interface Options {
[key: string]: string | string[] | boolean;
}

export interface AvailableSubcommands {
[key: string]: {
export interface ValuedOptions {
name: string,
alias: string[],
description: string,
value: string,
}

export interface Subcommand {
description: string;
valuedOptions?: ValuedOptions[];
options: {
name: string;
description: string;
options: {
name: string;
description: string;
}[];
}
valuedOptions?: ValuedOptions[];
}[];
}

export interface AvailableSubcommands {
[key: string]: Subcommand;
}

export type Platform = 'windows' | 'linux' | 'mac';
Expand Down
74 changes: 74 additions & 0 deletions src/commands/android/subcommands/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import colors from 'ansi-colors';

import Logger from '../../../logger';
import {AVAILABLE_SUBCOMMANDS} from '../constants';
import {Options} from '../interfaces';
import {getSubcommandOptionsHelp} from '../utils/common';

export function showHelp(subcommand: string) {
const subcmd = AVAILABLE_SUBCOMMANDS[subcommand];

Logger.log(`Usage: ${colors.cyan(`npx @nightwatch/mobile-helper android ${subcommand} [options]`)}\n`);
Logger.log(colors.yellow('Options:'));

const subcmdOptionsHelp = getSubcommandOptionsHelp(subcmd);
Logger.log(subcmdOptionsHelp);
}

export function verifyOptions(subcommand: string, options: Options): boolean {
const optionsPassed = Object.keys(options).filter(option => options[option] !== false);
const availableOptions = AVAILABLE_SUBCOMMANDS[subcommand].options;

const availableOptionsNames = availableOptions.map(option => option.name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you're only considering the main name of the options here, wouldn't it cause the corresponding aliases to be moved to the valuedOptionsPassed instead of the mainOptionsPassed, which would result in unknown valued option passed error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that should be an issue here since we haven't decided to use any aliases for the subcommand options. Aliases exists only for the flags (valued options).


// Divide the optionsPassed array in two arrays: mainOptionsPassed and valuedOptionsPassed.
// mainOptionsPassed contains the main option that is available for the subcommand.
// valuedOptionsPassed contains the options with string values corresponding to the main option.

const mainOptionsPassed = optionsPassed.filter(option => availableOptionsNames.includes(option));
const valuedOptionsPassed = optionsPassed.filter(option => !availableOptionsNames.includes(option));

if (mainOptionsPassed.length > 1) {
// A subcommand can only have one main option.
Logger.log(`${colors.red('Too many options passed:')} ${mainOptionsPassed.join(', ')}`);
showHelp(subcommand);

return false;
} else if (mainOptionsPassed.length === 0) {
// If the main option is not present, then any other options present are invalid.
Logger.log(`${colors.red('Unknown option(s) passed:')} ${valuedOptionsPassed.join(', ')}`);
showHelp(subcommand);

return false;
}

const mainOption = mainOptionsPassed[0];
const availableValuedOptions = availableOptions.find(option => option.name === mainOption)?.valuedOptions;

if (availableValuedOptions?.length) {
// If the main option has valued options, then check if the passed valued options are valid.
const valuedOptionsNames = availableValuedOptions.map(option => option.name);
const valuedOptionsAliases: string[] = [];

availableValuedOptions.forEach(option => valuedOptionsAliases.push(...option.alias));
valuedOptionsNames.push(...valuedOptionsAliases);

const unknownValuedOptions = valuedOptionsPassed.filter(option => !valuedOptionsNames.includes(option));

if (unknownValuedOptions.length) {
Logger.log(`${colors.red('Unknown option(s) passed:')} ${unknownValuedOptions.join(', ')}`);
showHelp(subcommand);

return false;
}
} else if (!availableValuedOptions?.length && valuedOptionsPassed.length) {
// If the main option does not have valued options, then all the other options present are invalid.
Logger.log(`${colors.red('Unknown option(s) passed:')} ${valuedOptionsPassed.join(', ')}`);
showHelp(subcommand);

return false;
}

return true;
}

8 changes: 7 additions & 1 deletion src/commands/android/subcommands/connect/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import {connectWirelessAdb} from './wireless';
import {Options, Platform} from '../../interfaces';
import {verifyOptions} from '../common';
import {connectWirelessAdb} from './wireless';

export async function connect(options: Options, sdkRoot: string, platform: Platform): Promise<boolean> {
const optionsVerified = verifyOptions('connect', options);
if (!optionsVerified) {
return false;
}

if (options.wireless) {
return await connectWirelessAdb(sdkRoot, platform);
}
Expand Down
12 changes: 9 additions & 3 deletions src/commands/android/subcommands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import colors from 'ansi-colors';
import * as dotenv from 'dotenv';
import path from 'path';

import {checkJavaInstallation, getSdkRootFromEnv} from '../utils/common';
import {connect} from './connect';
import {getPlatformName} from '../../../utils';
import Logger from '../../../logger';
import {getPlatformName} from '../../../utils';
import {Options, Platform} from '../interfaces';
import {checkJavaInstallation, getSdkRootFromEnv} from '../utils/common';
import {showHelp} from './common';
import {connect} from './connect';

export class AndroidSubcommand {
sdkRoot: string;
Expand All @@ -26,6 +27,11 @@ export class AndroidSubcommand {
}

async run(): Promise<boolean> {
if (this.options.help) {
showHelp(this.subcommand);

return true;
}
this.loadEnvFromDotEnv();

const javaInstalled = checkJavaInstallation(this.rootDir);
Expand Down
37 changes: 24 additions & 13 deletions src/commands/android/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ import path from 'path';
import untildify from 'untildify';
import which from 'which';

import {symbols} from '../../../utils';
import {ABI, AVAILABLE_OPTIONS, AVAILABLE_SUBCOMMANDS, DEFAULT_CHROME_VERSIONS, DEFAULT_FIREFOX_VERSION, SDK_BINARY_LOCATIONS} from '../constants';
import {Platform, SdkBinary} from '../interfaces';
import Logger from '../../../logger';
import {symbols} from '../../../utils';
import {
ABI, AVAILABLE_OPTIONS, AVAILABLE_SUBCOMMANDS,
DEFAULT_CHROME_VERSIONS, DEFAULT_FIREFOX_VERSION, SDK_BINARY_LOCATIONS
} from '../constants';
import {Platform, SdkBinary, Subcommand} from '../interfaces';

export const getAllAvailableOptions = () => {
const mainOptions = Object.keys(AVAILABLE_OPTIONS);
Expand Down Expand Up @@ -216,24 +219,32 @@ export const getSubcommandHelp = (): string => {
output += ' The following subcommands are used for different operations on Android SDK:\n\n';
output += `${colors.yellow('Subcommands and Subcommand-Options:')}\n`;

const longest = (xs: string[]) => Math.max.apply(null, xs.map(x => x.length));

Object.keys(AVAILABLE_SUBCOMMANDS).forEach(subcommand => {
const subcmd = AVAILABLE_SUBCOMMANDS[subcommand];
const subcmdOptions = subcmd.options?.map(option => `[--${option.name}]`).join(' ') || '';

output += ` ${colors.cyan(subcommand)} ${subcmdOptions}\n`;
output += ` ${colors.gray(subcmd.description)}\n`;

if (subcmd.options && subcmd.options.length > 0) {
const optionLongest = longest(subcmd.options.map(option => `--${option.name}`));
subcmd.options.forEach(option => {
const optionStr = `--${option.name}`;
const optionPadding = new Array(Math.max(optionLongest - optionStr.length + 3, 0)).join('.');
output += ` ${optionStr} ${colors.grey(optionPadding)} ${colors.gray(option.description)}\n`;
});
}
output += getSubcommandOptionsHelp(subcmd);
});

return output;
};

export const getSubcommandOptionsHelp = (subcmd: Subcommand) => {
let output = '';
const longest = (xs: string[]) => Math.max.apply(null, xs.map(x => x.length));

if (subcmd.options && subcmd.options.length > 0) {
const optionLongest = longest(subcmd.options.map(option => `--${option.name}`));
subcmd.options.forEach(option => {
const optionStr = `--${option.name}`;
const optionPadding = new Array(Math.max(optionLongest - optionStr.length + 3, 0)).join('.');
output += ` ${optionStr} ${colors.grey(optionPadding)} ${colors.gray(option.description)}\n`;
});
}

return output;
};

Loading