Skip to content

Commit

Permalink
feat: move markdown generator to dev-cli
Browse files Browse the repository at this point in the history
  • Loading branch information
jdx committed Feb 6, 2018
1 parent 1d8133b commit 019b4a3
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 958 deletions.
10 changes: 4 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
},
"devPlugins": [
"@anycli/plugin-plugins",
"@anycli/plugin-legacy",
"@heroku-cli/plugin-apps"
"@anycli/plugin-legacy"
]
},
"bugs": "https://github.com/anycli/plugin-help/issues",
Expand All @@ -28,14 +27,13 @@
"wrap-ansi": "^3.0.1"
},
"devDependencies": {
"@anycli/config": "^1.3.11",
"@anycli/config": "^1.3.15",
"@anycli/dev-cli": "^0.2.2",
"@anycli/errors": "^0.2.1",
"@anycli/plugin-legacy": "^0.0.2",
"@anycli/plugin-legacy": "^0.0.3",
"@anycli/plugin-plugins": "^0.2.13",
"@anycli/test": "^0.10.11",
"@anycli/tslint": "^0.2.6",
"@heroku-cli/plugin-apps": "^2.4.23",
"@anycli/tslint": "^0.2.7",
"@types/chai": "^4.1.2",
"@types/indent-string": "^3.0.0",
"@types/lodash.template": "^4.4.3",
Expand Down
35 changes: 17 additions & 18 deletions src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as Config from '@anycli/config'
import chalk from 'chalk'

import {Article, HelpOptions, Section} from '.'
import {castArray, compact, sortBy, uniqBy} from './util'
import {castArray, compact, sortBy, template, uniqBy} from './util'

const {
underline,
Expand All @@ -11,7 +11,10 @@ const {
} = chalk

export default class CommandHelp {
constructor(public config: Config.IConfig, public opts: HelpOptions) {}
render: (input: string) => string
constructor(public config: Config.IConfig, public opts: HelpOptions) {
this.render = template(this)
}

command(cmd: Config.Command): Article {
const flagDefs = cmd.flags || {}
Expand All @@ -23,7 +26,7 @@ export default class CommandHelp {
})
const args = (cmd.args || []).filter(a => !a.hidden)
return {
title: cmd.title,
title: cmd.description && this.render(cmd.description).split('\n')[0],
sections: compact([
this.usage(cmd, flags),
this.args(args),
Expand All @@ -43,13 +46,12 @@ export default class CommandHelp {
return {
heading: 'Usage',
type: 'code',
body: cmd.usage ? castArray(cmd.usage) : this.defaultUsage(cmd, flags)
body: (cmd.usage ? castArray(cmd.usage) : [this.defaultUsage(cmd, flags)])
.map(u => `$ ${this.config.bin} ${u}`)
}
}
protected defaultUsage(command: Config.Command, flags: Config.Command.Flag[]): string {
return compact([
'$',
this.config.bin,
command.id,
command.args.filter(a => !a.hidden).map(a => this.arg(a)).join(' '),
flags.length && '[OPTIONS]',
Expand All @@ -58,10 +60,11 @@ export default class CommandHelp {
}

protected description(cmd: Config.Command): Section | undefined {
if (!cmd.description) return
let description = cmd.description && this.render(cmd.description).split('\n').slice(1).join('\n')
if (!description) return
return {
heading: 'Description',
body: cmd.description.trim(),
body: description,
}
}

Expand Down Expand Up @@ -128,16 +131,12 @@ export default class CommandHelp {
commands = sortBy(commands, c => c.id)
commands = uniqBy(commands, c => c.id)
if (!commands.length) return
if (this.opts.format === 'markdown') {
return {
heading: 'commands',
body: commands.map(c => compact([`* [${c.id}](#${c.id})`, c.title]).join(' - '))
}
} else {
return {
heading: 'commands',
body: commands.map(c => [c.id, c.title]),
}
return {
heading: 'commands',
body: commands.map(c => [
c.id,
c.description && this.render(c.description.split('\n')[0])
]),
}
}
}
7 changes: 2 additions & 5 deletions src/commands/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ import {Command, flags} from '@anycli/command'
import Help from '..'

export default class HelpCommand extends Command {
static title = 'display help for <%= config.bin %>'
static description = 'display help for <%= config.bin %>'
static flags = {
all: flags.boolean({description: 'see all commands in CLI'}),
format: flags.enum({description: 'output in a different format', options: ['markdown', 'screen']}),
// format: flags.enum({description: 'output in a different format', options: ['markdown', 'man']}),
}
static args = [
{name: 'command', required: false, description: 'command to show help for'}
Expand All @@ -16,8 +14,7 @@ export default class HelpCommand extends Command {

async run() {
const {flags, argv} = this.parse(HelpCommand)
const format = flags.format as any || 'screen'
let help = new Help(this.config, {format, all: flags.all})
let help = new Help(this.config, {all: flags.all})
help.showHelp(argv)
}
}
167 changes: 33 additions & 134 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ import * as Config from '@anycli/config'
import {error} from '@anycli/errors'
import chalk from 'chalk'
import indent = require('indent-string')
import template = require('lodash.template')
import stripAnsi = require('strip-ansi')

import CommandHelp from './command'
import {renderList} from './list'
import RootHelp from './root'
import {stdtermwidth} from './screen'
import {castArray, compact, sortBy, uniqBy} from './util'

const width = require('string-width')
const wrap = require('wrap-ansi')

export interface Article {
Expand All @@ -28,20 +26,7 @@ const {
bold,
} = chalk

const widestLine = require('widest-line')

// function* ansiSplit(input: string, char: string) {
// // tslint:disable no-constant-condition
// while (true) {
// let idx = input.indexOf(char)
// if (idx === -1) return input
// yield sliceAnsi(input, 0, idx)
// input = sliceAnsi(input, idx, input.length)
// }
// }

export interface HelpOptions {
format?: 'markdown' | 'screen' | 'man'
all?: boolean
}

Expand Down Expand Up @@ -71,21 +56,14 @@ export default class Help {
commands = sortBy(commands, c => c.id)
commands = uniqBy(commands, c => c.id)
console.log(this.root(commands))
console.log()
if (this.opts.format === 'markdown') {
for (let command of commands) {
console.log(this.command(command))
console.log()
}
}
} else if (command = this.config.findCommand(subject)) {
console.log(this.command(command))
} else if (topic = this.config.findTopic(subject)) {
console.log(this.topic(topic))
} else {
error(`command ${subject} not found`)
}
if (this.opts.format === 'screen') console.log()
console.log()
}

root(commands: Config.Command[]): string {
Expand All @@ -101,65 +79,22 @@ export default class Help {
command(command: Config.Command): string {
const help = new CommandHelp(this.config, this.opts)
const article = help.command(command)
console.log(command.id)
console.log('-'.repeat(width(command.id)))
console.log()
return this.render(article)
}

protected render(article: Article): string {
switch (this.opts.format) {
case 'markdown': return this.renderMarkdown(article)
case 'man':
case 'screen':
default: return this.renderScreen(article)
}
}

protected renderMarkdown(article: Article): string {
const maxWidth = 100
return [
stripAnsi(this.renderTemplate(article.title)),
'',
...article.sections
.map(s => {
let body = '\n'
if (s.body.length === 0) {
body += ''
} else if (Array.isArray(s.body[0])) {
body += '```\n'
body += this.renderList(s.body as any, {maxWidth: maxWidth - 2, stripAnsi: true})
body += '\n```'
} else {
let output = castArray(s.body as string).join('\n')
output = this.renderTemplate(output)
body += wrap(stripAnsi(output), maxWidth - 2, {trim: false, hard: true})
}
if (s.type === 'code') {
body = `\n\`\`\`sh-session${body}\n\`\`\``
}
return compact([
`**${s.heading}**`,
body,
]).join('\n') + '\n'
})
].join('\n').trim()
}

protected renderScreen(article: Article): string {
const maxWidth = stdtermwidth
return compact([
this.renderTemplate(article.title),
article.title,
...article.sections
.map(s => {
let body
if (s.body.length === 0) {
body = ''
} else if (Array.isArray(s.body[0])) {
body = this.renderList(s.body as any, {maxWidth: maxWidth - 2})
body = renderList(s.body as any, {maxWidth: maxWidth - 2})
} else {
body = castArray(s.body as string).join('\n')
body = this.renderTemplate(body)
body = wrap(body, maxWidth - 2, {trim: false, hard: true})
}
return compact([
Expand All @@ -170,69 +105,33 @@ export default class Help {
]).join('\n\n')
}

protected renderTemplate(t: string | undefined): string {
return template(t || '')({config: this.config})
}

protected renderList(input: (string | undefined)[][], opts: {maxWidth: number, multiline?: boolean, stripAnsi?: boolean}): string {
if (input.length === 0) {
return ''
}
input = input.map(([left, right]) => {
return [this.renderTemplate(left), this.renderTemplate(right)]
})
const renderMultiline = () => {
output = ''
for (let [left, right] of input) {
if (!left && !right) continue
if (left) {
if (opts.stripAnsi) left = stripAnsi(left)
output += wrap(left.trim(), opts.maxWidth, {hard: true, trim: false})
}
if (right) {
if (opts.stripAnsi) right = stripAnsi(right)
output += '\n'
output += indent(wrap(right.trim(), opts.maxWidth - 2, {hard: true, trim: false}), 4)
}
output += '\n\n'
}
return output.trim()
}
if (opts.multiline) return renderMultiline()
const maxLength = widestLine(input.map(i => i[0]).join('\n'))
let output = ''
let spacer = '\n'
let cur = ''
for (let [left, right] of input) {
if (cur) {
output += spacer
output += cur
}
cur = left || ''
if (opts.stripAnsi) cur = stripAnsi(cur)
if (!right) {
cur = cur.trim()
continue
}
if (opts.stripAnsi) right = stripAnsi(right)
right = wrap(right.trim(), opts.maxWidth - (maxLength + 2), {hard: true, trim: false})
// right = wrap(right.trim(), screen.stdtermwidth - (maxLength + 4), {hard: true, trim: false})
const [first, ...lines] = right!.split('\n').map(s => s.trim())
cur += ' '.repeat(maxLength - width(cur) + 2)
cur += first
if (lines.length === 0) {
continue
}
// if we start putting too many lines down, render in multiline format
if (lines.length > 4) return renderMultiline()
spacer = '\n\n'
cur += '\n'
cur += indent(lines.join('\n'), maxLength + 2)
}
if (cur) {
output += spacer
output += cur
}
return output.trim()
}
// protected renderMarkdown(article: Article): string {
// const maxWidth = 100
// return [
// stripAnsi(this.renderTemplate(article.title)),
// '',
// ...article.sections
// .map(s => {
// let body = '\n'
// if (s.body.length === 0) {
// body += ''
// } else if (Array.isArray(s.body[0])) {
// body += '```\n'
// body += renderList(s.body as any, {maxWidth: maxWidth - 2, stripAnsi: true})
// body += '\n```'
// } else {
// let output = castArray(s.body as string).join('\n')
// output = this.renderTemplate(output)
// body += wrap(stripAnsi(output), maxWidth - 2, {trim: false, hard: true})
// }
// if (s.type === 'code') {
// body = `\n\`\`\`sh-session${body}\n\`\`\``
// }
// return compact([
// `**${s.heading}**`,
// body,
// ]).join('\n') + '\n'
// })
// ].join('\n').trim()
// }
}
Loading

0 comments on commit 019b4a3

Please sign in to comment.