Skip to content

Commit

Permalink
feat: add topic separator option (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
RasPhilCo committed Feb 27, 2021
1 parent 0b4f357 commit b3ca07f
Show file tree
Hide file tree
Showing 13 changed files with 991 additions and 14 deletions.
4 changes: 4 additions & 0 deletions src/config/config.ts
Expand Up @@ -90,6 +90,8 @@ export class Config implements IConfig {

valid!: boolean

topicSeparator: ':' | ' ' = ':'

protected warned = false

private _commands?: Command.Plugin[]
Expand Down Expand Up @@ -126,6 +128,8 @@ export class Config implements IConfig {
this.windows = this.platform === 'win32'
this.bin = this.pjson.oclif.bin || this.name
this.dirname = this.pjson.oclif.dirname || this.name
// currently, only colons or spaces are valid separators
if (this.pjson.oclif.topicSeparator && [':', ' '].includes(this.pjson.oclif.topicSeparator)) this.topicSeparator = this.pjson.oclif.topicSeparator!
if (this.platform === 'win32') this.dirname = this.dirname.replace('/', '\\')
this.userAgent = `${this.name}/${this.version} ${this.platform}-${this.arch} node-${process.version}`
this.shell = this._shell()
Expand Down
36 changes: 25 additions & 11 deletions src/help/index.ts
Expand Up @@ -9,9 +9,9 @@ import {renderList} from './list'
import RootHelp from './root'
import {stdtermwidth} from './screen'
import {compact, sortBy, uniqBy} from '../util'
import {template} from './util'
import {standarizeIDFromArgv, template} from './util'

export {getHelpClass} from './util'
export {standarizeIDFromArgv, getHelpClass} from './util'

const wrap = require('wrap-ansi')
const {
Expand All @@ -29,6 +29,7 @@ function getHelpSubject(args: string[]): string | undefined {

export abstract class HelpBase {
constructor(config: Interfaces.Config, opts: Partial<Interfaces.HelpOptions> = {}) {
if (!config.topicSeparator) config.topicSeparator = ':' // back-support @oclif/config
this.config = config
this.opts = {maxWidth: stdtermwidth, ...opts}
}
Expand Down Expand Up @@ -93,6 +94,7 @@ export class Help extends HelpBase {
}

public showHelp(argv: string[]) {
if (this.config.topicSeparator !== ':') argv = standarizeIDFromArgv(argv, this.config)
const subject = getHelpSubject(argv)
if (!subject) {
if (this.config.pjson.oclif.default) {
Expand Down Expand Up @@ -191,17 +193,24 @@ export class Help extends HelpBase {
}

protected formatCommand(command: Interfaces.Command): string {
if (this.config.topicSeparator !== ':') {
command.id = command.id.replace(/:/g, this.config.topicSeparator)
command.aliases = command.aliases && command.aliases.map(a => a.replace(/:/g, this.config.topicSeparator))
}
const help = new CommandHelp(command, this.config, this.opts)
return help.generate()
}

protected formatCommands(commands: Interfaces.Command[]): string {
if (commands.length === 0) return ''

const body = renderList(commands.map(c => [
c.id,
c.description && this.render(c.description.split('\n')[0]),
]), {
const body = renderList(commands.map(c => {
if (this.config.topicSeparator !== ':') c.id = c.id.replace(/:/g, this.config.topicSeparator)
return [
c.id,
c.description && this.render(c.description.split('\n')[0]),
]
}), {
spacer: '\n',
stripAnsi: this.opts.stripAnsi,
maxWidth: this.opts.maxWidth - 2,
Expand All @@ -217,11 +226,13 @@ export class Help extends HelpBase {
let description = this.render(topic.description || '')
const title = description.split('\n')[0]
description = description.split('\n').slice(1).join('\n')
let topicID = `${topic.name}:COMMAND`
if (this.config.topicSeparator !== ':') topicID = topicID.replace(/:/g, this.config.topicSeparator)
let output = compact([
title,
[
bold('USAGE'),
indent(wrap(`$ ${this.config.bin} ${topic.name}:COMMAND`, this.opts.maxWidth - 2, {trim: false, hard: true}), 2),
indent(wrap(`$ ${this.config.bin} ${topicID}`, this.opts.maxWidth - 2, {trim: false, hard: true}), 2),
].join('\n'),
description && ([
bold('DESCRIPTION'),
Expand All @@ -234,10 +245,13 @@ export class Help extends HelpBase {

protected formatTopics(topics: Interfaces.Topic[]): string {
if (topics.length === 0) return ''
const body = renderList(topics.map(c => [
c.name,
c.description && this.render(c.description.split('\n')[0]),
]), {
const body = renderList(topics.map(c => {
if (this.config.topicSeparator !== ':') c.name = c.name.replace(/:/g, this.config.topicSeparator)
return [
c.name,
c.description && this.render(c.description.split('\n')[0]),
]
}), {
spacer: '\n',
stripAnsi: this.opts.stripAnsi,
maxWidth: this.opts.maxWidth - 2,
Expand Down
29 changes: 29 additions & 0 deletions src/help/util.ts
Expand Up @@ -39,3 +39,32 @@ export function template(context: any): (t: string) => string {
}
return render
}

function collateSpacedCmdIDFromArgs(argv: string[], config: IConfig): string[] {
if (argv.length === 1) return argv

const ids = config.commandIDs.concat(config.topics.map(t => t.name))

const findId = (id: string, next: string[]): string | undefined => {
const idPresnet = (id: string) => ids.includes(id)
if (idPresnet(id) && !idPresnet(`${id}:${next[0]}`)) return id
if (next.length === 0 || next[0] === '--') return
return findId(`${id}:${next[0]}`, next.slice(1))
}

const id = findId(argv[0], argv.slice(1))

if (id) {
const argvSlice = argv.slice(id.split(':').length)
return [id, ...argvSlice]
}

return argv // ID is argv[0]
}

export function standarizeIDFromArgv(argv: string[], config: IConfig): string[] {
if (argv.length === 0) return argv
if (config.topicSeparator === ' ') argv = collateSpacedCmdIDFromArgs(argv, config)
else if (config.topicSeparator !== ':') argv[0] = argv[0].replace(new RegExp(config.topicSeparator, 'g'), ':')
return argv
}
1 change: 1 addition & 0 deletions src/interfaces/config.ts
Expand Up @@ -88,6 +88,7 @@ export interface Config {
plugins: Plugin[];
binPath?: string;
valid: boolean;
topicSeparator: ':' | ' ';
readonly commands: Command.Plugin[];
readonly topics: Topic[];
readonly commandIDs: string[];
Expand Down
1 change: 1 addition & 0 deletions src/interfaces/pjson.ts
Expand Up @@ -14,6 +14,7 @@ export namespace PJSON {
schema?: number;
title?: string;
description?: string;
topicSeparator?: ':' | ' ';
hooks?: { [name: string]: (string | string[]) };
commands?: string;
default?: string;
Expand Down
3 changes: 2 additions & 1 deletion src/main.ts
Expand Up @@ -2,7 +2,7 @@ import {format, inspect} from 'util'

import * as Interfaces from './interfaces'
import {Config} from './config'
import {getHelpClass} from './help'
import {getHelpClass, standarizeIDFromArgv} from './help'

const log = (message = '', ...args: any[]) => {
// tslint:disable-next-line strict-type-predicates
Expand All @@ -29,6 +29,7 @@ export async function run(argv = process.argv.slice(2), options?: Interfaces.Loa
const config = await Config.load(options || (module.parent && module.parent.parent && module.parent.parent.filename) || __dirname) as Config

// run init hook
if (config.topicSeparator !== ':') argv = standarizeIDFromArgv(argv, config)
let [id, ...argvSlice] = argv
await config.runHook('init', {id, argv: argvSlice})

Expand Down
29 changes: 29 additions & 0 deletions test/command/fixtures/typescript/package.json
@@ -0,0 +1,29 @@
{
"name": "oclif",
"version": "0.0.0",
"description": "base library for oclif CLIs",
"private": true,
"files": [],
"oclif": {
"commands": "./lib/commands",
"topicSeparator": " ",
"plugins": [
"@oclif/plugin-help",
"@oclif/plugin-plugins"
],
"topics": {
"foo": {
"description": "foo topic description",
"subtopics": {
"bar": {
"description": "foo bar topic description"
}
}
}
}
},
"devDependencies": {
"globby": "^8.0.1",
"ts-node": "^6.0.2"
}
}
8 changes: 8 additions & 0 deletions test/command/fixtures/typescript/src/commands/foo/bar/fail.ts
@@ -0,0 +1,8 @@
export class Command {
static description = 'fail description'

static run() {
console.log('it fails!')
throw new Error('random error')
}
}
@@ -0,0 +1,8 @@
export class Command {
static description = 'succeed description'

static run() {
console.log('it works!')
return 'returned success!'
}
}
7 changes: 7 additions & 0 deletions test/command/fixtures/typescript/src/commands/foo/baz.ts
@@ -0,0 +1,7 @@
export class Command {
static description = 'foo baz description'

static run() {
console.log('running Baz')
}
}
11 changes: 11 additions & 0 deletions test/command/fixtures/typescript/tsconfig.json
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"outDir": "./lib",
"rootDirs": [
"./src"
]
},
"include": [
"./src/**/*"
]
}

0 comments on commit b3ca07f

Please sign in to comment.