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

feat: add topic separator option #111

Merged
merged 2 commits into from
Feb 27, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
23 changes: 20 additions & 3 deletions src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,16 @@ export class Config implements IConfig {

valid!: boolean

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

protected warned = false

private _commands?: Command.Plugin[]

private _commandIDs?: string[]

private _topics?: Topic[]

// eslint-disable-next-line no-useless-constructor
constructor(public options: Options) {}

Expand Down Expand Up @@ -120,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 Expand Up @@ -294,14 +304,20 @@ export class Config implements IConfig {
}

get commands(): Command.Plugin[] {
return flatMap(this.plugins, p => p.commands)
if (this._commands) return this._commands
this._commands = flatMap(this.plugins, p => p.commands)
return this._commands
}

get commandIDs() {
return uniq(this.commands.map(c => c.id))
if (this._commandIDs) return this._commandIDs
const ids = Lodash.flattenDeep(this.commands.map(c => [c.id, c.aliases]))
RasPhilCo marked this conversation as resolved.
Show resolved Hide resolved
this._commandIDs = uniq(ids)
return this._commandIDs
}

get topics(): Topic[] {
if (this._topics) return this._topics
const topics: Topic[] = []
for (const plugin of this.plugins) {
for (const topic of compact(plugin.topics)) {
Expand All @@ -323,7 +339,8 @@ export class Config implements IConfig {
parts.pop()
}
}
return topics
this._topics = topics
return this._topics
}

s3Key(type: keyof PJSON.S3.Templates, ext?: '.tar.gz' | '.tar.xz' | IConfig.s3Key.Options, options: IConfig.s3Key.Options = {}) {
Expand Down
36 changes: 25 additions & 11 deletions src/help/index.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this also return id?

Copy link
Contributor Author

@RasPhilCo RasPhilCo Feb 27, 2021

Choose a reason for hiding this comment

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

This is returning no match b/c the line would return the last valid id found. This line is saying, you didn't find anything to return (line above it) and you have no further argv to explore.

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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export class Command {
static description = 'fail description'

static run() {
console.log('it fails!')
throw new Error('random error')
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class Command {
static description = 'foo baz description'

static run() {
console.log('it works!')
}
}
11 changes: 11 additions & 0 deletions test/command/fixtures/typescript/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"outDir": "./lib",
"rootDirs": [
"./src"
]
},
"include": [
"./src/**/*"
]
}
Loading