Skip to content

Commit

Permalink
feat(help): add utility to create help screens
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Apr 12, 2019
1 parent 2223f24 commit 35571b8
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 38 deletions.
63 changes: 63 additions & 0 deletions packages/ace/example/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* @adonisjs/ace
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { printHelp } from '../src/utils/help'
import { BaseCommand } from '../src/BaseCommand'
import { Kernel } from '../src/Kernel'

class Greet extends BaseCommand {
public static commandName = 'greet'
public static description = 'Greet a user with their name'
public static args = [
{
name: 'name',
description: 'The name of the person you want to greet',
required: true,
},
{
name: 'age',
required: false,
},
]

public static flags = [
{
name: 'env',
description: 'The environment to use to specialize certain commands',
type: 'boolean',
},
{
name: 'entrypoint',
description: 'The main HTML file that will be requested',
type: 'string',
},
{
name: 'fragment',
alias: 'f',
description: 'HTML fragments loaded on demand',
type: 'array',
},
]
}

class MakeController extends BaseCommand {
public static commandName = 'make:controller'
public static description = 'Create a HTTP controller'
}

class MakeModel extends BaseCommand {
public static commandName = 'make:model'
public static description = 'Create database model'
}

const kernel = new Kernel()
kernel.flag('env', () => {}, { type: 'string' })
printHelp([Greet, MakeController, MakeModel], Object.keys(kernel.flags).map((flag) => {
return kernel.flags[flag]
}))
102 changes: 64 additions & 38 deletions packages/ace/src/utils/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,68 @@

import * as padRight from 'pad-right'
import { green, bold, yellow, dim } from 'kleur'
import { CommandConstructorContract, CommandArg } from '../Contracts'
import { sortAndGroupCommands } from './sortAndGroupCommands'
import { CommandConstructorContract, CommandArg, CommandFlag } from '../Contracts'

/**
* Wraps the command arg inside `<>` or `[]` brackets based upon if it's
* required or not.
*/
function wrapArg (arg: CommandArg): string {
return arg.required ? `<${arg.name}>` : `[${arg.name}]`
}

function getFlagsForDisplay (flags: CommandFlag[]) {
return flags.map(({ name, type, alias, description }) => {
/**
* Display name is the way we want to display a single flag in the
* list of flags
*/
const displayName = alias ? `-${alias}, --${name}` : `--${name}`

/**
* The type hints the user about the expectation on the flag type. We only
* print the type, when flag is not a boolean.
*/
const displayType = type === 'array' ? 'string[]' : type === 'string' ? 'string' : ''

return {
displayName,
displayType,
description,
width: displayName.length + displayType.length,
}
})
}

function getArgsForDisplay (args: CommandArg[]) {
return args.map(({ name, description }) => {
return {
displayName: name,
description: description,
width: name.length,
}
})
}

function getCommandsForDisplay (commands: CommandConstructorContract[]) {
return commands.map(({ commandName, description }) => {
return { displayName: commandName, description, width: commandName.length }
})
}

/**
* Prints help for all the commands by sorting them in alphabetical order
* and grouping them as per their namespace.
*/
export function printHelp (commands: CommandConstructorContract[]): void {
export function printHelp (commands: CommandConstructorContract[], flags: CommandFlag[]): void {
const flagsList = getFlagsForDisplay(flags)
const commandsList = getCommandsForDisplay(commands)

/**
* Get width of longest command name.
*/
const maxWidth = Math.max.apply(Math, commands.map(({ commandName }) => commandName.length))
const maxWidth = Math.max.apply(Math, flagsList.concat(commandsList as any).map(({ width }) => width))

/**
* Sort commands and group them, so that we can print them as per
Expand All @@ -38,14 +88,16 @@ export function printHelp (commands: CommandConstructorContract[]): void {
console.log(` ${green(padRight(commandName, maxWidth, ' '))} ${dim(description)}`)
})
})
}

/**
* Wraps the command arg inside `<>` or `[]` brackets based upon if it's
* required or not.
*/
function wrapArg (arg: CommandArg): string {
return arg.required ? `<${arg.name}>` : `[${arg.name}]`
if (flagsList.length) {
console.log('')
console.log(bold(yellow('Global Flags')))

flagsList.forEach(({ displayName, displayType, description = '', width }) => {
const whiteSpace = padRight('', maxWidth - width, ' ')
console.log(` ${green(displayName)} ${dim(displayType)} ${whiteSpace} ${dim(description)}`)
})
}
}

/**
Expand All @@ -60,34 +112,8 @@ export function printHelpFor (command: CommandConstructorContract): void {
console.log('')
console.log(`${yellow('Usage:')} ${command.commandName} ${dim(command.args.map(wrapArg).join(' '))}`)

const flags = command.flags.map(({ name, type, alias, description }) => {
/**
* Display name is the way we want to display a single flag in the
* list of flags
*/
const displayName = alias ? `-${alias}, --${name}` : `--${name}`

/**
* The type hints the user about the expectation on the flag type. We only
* print the type, when flag is not a boolean.
*/
const displayType = type === 'array' ? 'string[]' : type === 'string' ? 'string' : ''

return {
displayName,
displayType,
description,
width: displayName.length + displayType.length,
}
})

const args = command.args.map(({ name, description }) => {
return {
displayName: name,
description: description,
width: name.length,
}
})
const flags = getFlagsForDisplay(command.flags)
const args = getArgsForDisplay(command.args)

/**
* Getting max width to keep flags and args symmetric
Expand Down

0 comments on commit 35571b8

Please sign in to comment.