进程管理程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
pmt/cmd/print.go

389 lines
7.9 KiB

package cmd
import (
"errors"
"fmt"
"github.com/mattn/go-isatty"
"io"
"os"
"regexp"
"strings"
)
var (
colorRE = regexp.MustCompile(`\x1b[;?[0-9]+m`)
defaultShowOptions = &ShowOptions{Colors: true, LineWidth: 80, Titles: true}
)
type ShowWriter interface {
io.Writer
Fd() uintptr
}
type ShowOptions struct {
Colors bool
Types bool
Long bool
Titles bool
LineWidth int
Writer ShowWriter
}
func length(str string) int {
return len(colorRE.ReplaceAllString(str, ""))
}
func colorize(enable bool, color int, str string) string {
if enable {
return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", color, str)
}
return str
}
func Blue(str string, opts ...*ShowOptions) string {
return colorize(resolveShowOptions(opts).Colors, 34, str)
}
func Red(str string, opts ...*ShowOptions) string {
return colorize(resolveShowOptions(opts).Colors, 31, str)
}
func Yellow(str string, opts ...*ShowOptions) string {
return colorize(resolveShowOptions(opts).Colors, 33, str)
}
func Cyan(str string, opts ...*ShowOptions) string {
return colorize(resolveShowOptions(opts).Colors, 35, str)
}
func Green(str string, opts ...*ShowOptions) string {
return colorize(resolveShowOptions(opts).Colors, 32, str)
}
func resolveShowOptions(opts []*ShowOptions) *ShowOptions {
for _, opt := range opts {
if opt.LineWidth == 0 {
opt.LineWidth = 80
}
return opt
}
return defaultShowOptions
}
func showMessage(opts *ShowOptions, f func(...*ShowOptions) string) error {
out := opts.Writer
if out == nil {
out = os.Stdout
}
if opts.Colors && (os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(out.Fd()) && !isatty.IsCygwinTerminal(out.Fd()))) {
opts.Colors = false
}
_, err := fmt.Fprintln(out, f(opts))
return err
}
func versionToString(cmd *Command, opts *ShowOptions) string {
var str string
if len(cmd.version.RuntimeValue) > 0 {
str = cmd.version.RuntimeValue[0]
} else {
str = cmd.version.DefaultValue.(string)
}
if opts.Colors {
str = Green(str, opts)
}
if opts.Titles {
str = "VERSION: " + str
}
return str
}
func usageToString(cmd *Command, opts *ShowOptions) string {
var str string
if opts.Titles {
str += "USAGE:"
}
if cmd.program != nil {
str += " " + Cyan(cmd.program.name, opts)
}
str += " " + Cyan(cmd.name, opts)
if len(cmd.options) > 0 {
str += " " + Yellow("[", opts) + Cyan("OPTIONS", opts) + Yellow("]", opts)
}
for _, a := range cmd.arguments {
str += " " + argumentToString(a, opts)
}
return strings.TrimSpace(str)
}
func argumentToString(a *Argument, opts *ShowOptions) string {
var str string
if a.Required {
str += Yellow("<", opts)
} else {
str += Yellow("[", opts)
}
str += Cyan(a.Name, opts)
if opts.Types && len(a.Type) > 0 {
str += ":"
if a.Variadic {
str += "..."
}
str += Green(a.Type, opts)
}
if a.Required {
str += Yellow(">", opts)
} else {
str += Yellow("]", opts)
}
return str
}
func helpToString(cmd *Command, opts *ShowOptions) string {
var str string
if usage := cmd.GetUsage(opts); len(usage) > 0 {
str += usage + "\n"
}
if version := cmd.GetVersion(opts); len(version) > 0 {
str += version + "\n"
}
if desc := cmd.GetDescription(opts); len(desc) > 0 {
str += "\n" + desc + "\n\n"
}
if options := optionsToString(cmd, opts); len(options) > 0 {
str += options + "\n"
}
if arguments := argumentsToString(cmd, opts); len(arguments) > 0 {
str += arguments + "\n"
}
if commands := commandsToString(cmd, opts); len(commands) > 0 {
str += commands + "\n"
}
return strings.TrimSpace(str) + "\n"
}
func commandsToString(cmd *Command, opts *ShowOptions) string {
if len(cmd.commands) == 0 {
return ""
}
tb := &table{
gaps: []string{" ", ""},
rows: [][]string{},
sizes: []int{0, 0},
}
for _, o := range cmd.commands {
tb.AddRow(
Cyan(o.name, opts),
o.description,
)
}
str := tb.String(" ", "", opts.LineWidth)
if opts.Titles {
return "SUBCOMMANDS:\n" + str
}
return str
}
func argumentsToString(cmd *Command, opts *ShowOptions) string {
if len(cmd.arguments) == 0 {
return ""
}
t := &table{
gaps: []string{" ", " ", ""},
sizes: []int{0, 0, 0},
rows: [][]string{},
}
if !opts.Types {
t.gaps[1] = ""
}
for _, a := range cmd.arguments {
var typ string
if opts.Types && len(a.Type) > 0 {
if a.Required {
typ += Yellow("<", opts)
} else {
typ += Yellow("[", opts)
}
typ += Cyan(a.Type, opts)
if a.Required {
typ += Yellow("<", opts)
} else {
typ += Yellow("[", opts)
}
}
t.AddRow(
Cyan(a.Name, opts),
typ,
a.Description,
)
}
if opts.Titles {
return "ARGUMENTS:\n" + t.String(" ", "", opts.LineWidth) + "\n"
}
return t.String(" ", "", opts.LineWidth) + "\n"
}
func optionsToString(cmd *Command, opts *ShowOptions) string {
if len(cmd.options) == 0 {
return ""
}
t := &table{
gaps: []string{" ", " ", ""},
sizes: []int{0, 0, 0},
rows: [][]string{},
}
for _, o := range cmd.options {
t.AddRow(
optionFlagsToString(o, opts),
argumentToString(o.toArgument(), opts),
o.Description,
)
}
str := t.String(" ", "", opts.LineWidth)
if opts.Titles {
return "OPTIONS:\n" + str + "\n"
}
return str + "\n"
}
func optionFlagsToString(o *Option, opts *ShowOptions) string {
var str string
if len(o.Short) > 0 {
str += Blue("-"+o.Short, opts) + ", "
} else {
// 保留 short 的位置
str += " "
}
return str + Blue("--"+o.Long, opts)
}
func ensureOption(c *Command, expr string, desc []string, owner string) (*Option, error) {
o1, err := createOption(expr)
if err != nil {
return nil, err
}
switch len(desc) {
case 0:
break
case 1:
o1.Description = desc[0]
default:
o1.Description = desc[0]
o1.LongDescription = desc[1]
}
for _, o2 := range c.options {
err := hasConflicts(o2, o1)
if err != nil {
if len(owner) > 0 {
return nil, errors.New(owner + " option conflicts with an optional parameter")
}
return nil, errors.New("option conflicts with another optional parameter")
}
}
return o1, nil
}
type table struct {
gaps []string
sizes []int
rows [][]string
}
var spaceRE = regexp.MustCompile(`[\t\n\v\f\r]+`)
var noNewlineSpaceRE = regexp.MustCompile(`[\t\v\f\r]+]`)
func (t *table) AddRow(columns ...string) {
l := len(columns)
width := len(t.sizes)
for i, column := range columns {
column = strings.TrimSpace(column)
if i < l-1 {
column = spaceRE.ReplaceAllString(column, "")
} else {
column = noNewlineSpaceRE.ReplaceAllString(column, "")
}
columns[i] = column
size := length(column)
if size > t.sizes[i] {
t.sizes[i] = size
}
}
for i := l; i < width; i++ {
columns = append(columns, "")
}
t.rows = append(t.rows, columns)
}
func (t *table) String(linePrefix, lineSuffix string, lineWidth int) string {
lastColumnIndex := len(t.sizes) - 1
lastColumnWidth := 0
var newlinePrefix string
var str string
for j, row := range t.rows {
line := linePrefix
for i, col := range row {
width := t.sizes[i]
// 不是最后一行
if i < lastColumnIndex {
line += col
count := length(col)
if count < width {
line += strings.Repeat(" ", width-count)
}
line += t.gaps[i]
continue
}
// 最后一行换行
if lastColumnWidth == 0 {
lastColumnWidth = lineWidth - length(line) - length(lineSuffix)
if lastColumnWidth < 10 {
lastColumnWidth = 10
}
newlinePrefix = strings.Repeat(" ", length(line))
}
for _, cline := range strings.Split(col, "\n") {
var s string
var w int
var x int
var y int
for _, word := range strings.Split(cline, " ") {
n := length(word)
if w+n > lastColumnWidth {
if y > 0 {
line += "\n" + newlinePrefix
}
line += s
y += 1
s = ""
w = 0
x = 0
continue
}
if x > 0 {
s += " "
}
s += word
w += n
x += 1
}
if x > 0 {
if y > 0 {
line += "\n" + newlinePrefix
}
line += s
}
}
}
if j > 0 {
str += "\n"
}
str += line
}
return str
}