mirror of https://github.com/rkfg/regolancer
add new help message instead of using builtin
parent
383d0ae221
commit
0c767188b1
@ -0,0 +1,312 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/jessevdk/go-flags"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
paddingBeforeOption = 2
|
||||
distanceBetweenOptionAndDescription = 2
|
||||
defaultShortOptDelimiter = '-'
|
||||
defaultLongOptDelimiter = "--"
|
||||
defaultNameArgDelimiter = '='
|
||||
)
|
||||
|
||||
type Option struct {
|
||||
Description string
|
||||
|
||||
ShortName rune
|
||||
|
||||
LongName string
|
||||
|
||||
Grouping string
|
||||
|
||||
field reflect.StructField
|
||||
|
||||
value reflect.Value
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
options []*Option
|
||||
}
|
||||
|
||||
func scanStruct(realval reflect.Value, opt *Options) error {
|
||||
|
||||
stype := realval.Type()
|
||||
|
||||
for i := 0; i < stype.NumField(); i++ {
|
||||
field := stype.Field(i)
|
||||
|
||||
longname := field.Tag.Get("long")
|
||||
shortname := field.Tag.Get("short")
|
||||
grouping := field.Tag.Get("rego-grouping")
|
||||
description := field.Tag.Get("description")
|
||||
|
||||
// Need at least either a short or long name
|
||||
if longname == "" && shortname == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
short := rune(0)
|
||||
rc := utf8.RuneCountInString(shortname)
|
||||
|
||||
if rc > 1 {
|
||||
return fmt.Errorf("short names can only be 1 character long, not `%s'",
|
||||
shortname)
|
||||
|
||||
} else if rc == 1 {
|
||||
short, _ = utf8.DecodeRuneInString(shortname)
|
||||
}
|
||||
|
||||
option := &Option{
|
||||
Description: description,
|
||||
ShortName: short,
|
||||
LongName: longname,
|
||||
Grouping: grouping,
|
||||
|
||||
field: field,
|
||||
value: realval.Field(i),
|
||||
}
|
||||
|
||||
opt.options = append(opt.options, option)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteHelp(options *Options, p *flags.Parser, writer io.Writer) (err error) {
|
||||
if writer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
wr := bufio.NewWriter(os.Stdout)
|
||||
aligninfo := getAlignmentInfo(options, p)
|
||||
|
||||
if p.Name != "" {
|
||||
wr.WriteString("Usage:\n")
|
||||
wr.WriteString(" ")
|
||||
fmt.Fprintf(wr, " %s [OPTIONS]\n", p.Name)
|
||||
}
|
||||
|
||||
fmt.Fprintf(wr, "\nApplication Options:\n")
|
||||
fmt.Fprintf(wr, "--------------------")
|
||||
aligninfo.indent = false
|
||||
for _, info := range options.options {
|
||||
if !info.showInHelp() {
|
||||
continue
|
||||
}
|
||||
|
||||
writeHelpOption(wr, info, aligninfo)
|
||||
}
|
||||
|
||||
wr.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
// }
|
||||
|
||||
func getAlignmentInfo(options *Options, p *flags.Parser) alignmentInfo {
|
||||
ret := alignmentInfo{
|
||||
maxLongLen: 0,
|
||||
hasShort: false,
|
||||
hasValueName: false,
|
||||
terminalColumns: getTerminalColumns(),
|
||||
}
|
||||
|
||||
if ret.terminalColumns <= 0 {
|
||||
ret.terminalColumns = 80
|
||||
}
|
||||
|
||||
for _, info := range options.options {
|
||||
if !info.showInHelp() {
|
||||
continue
|
||||
}
|
||||
|
||||
if info.ShortName != 0 {
|
||||
ret.hasShort = true
|
||||
}
|
||||
|
||||
l := info.LongName
|
||||
|
||||
ret.updateLen(l, false)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func getTerminalColumns() int {
|
||||
ws, err := unix.IoctlGetWinsize(0, unix.TIOCGWINSZ)
|
||||
if err != nil {
|
||||
return 80
|
||||
}
|
||||
return int(ws.Col)
|
||||
}
|
||||
|
||||
type alignmentInfo struct {
|
||||
maxLongLen int
|
||||
hasShort bool
|
||||
hasValueName bool
|
||||
terminalColumns int
|
||||
indent bool
|
||||
}
|
||||
|
||||
const (
|
||||
HelpFlag = 1
|
||||
)
|
||||
|
||||
func wrapText(s string, l int, prefix string) string {
|
||||
var ret string
|
||||
|
||||
if l < 10 {
|
||||
l = 10
|
||||
}
|
||||
|
||||
// Basic text wrapping of s at spaces to fit in l
|
||||
lines := strings.Split(s, "\n")
|
||||
|
||||
for _, line := range lines {
|
||||
var retline string
|
||||
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
for len(line) > l {
|
||||
// Try to split on space
|
||||
suffix := ""
|
||||
|
||||
pos := strings.LastIndex(line[:l], " ")
|
||||
|
||||
if pos < 0 {
|
||||
pos = l - 1
|
||||
suffix = "-\n"
|
||||
}
|
||||
|
||||
if len(retline) != 0 {
|
||||
retline += "\n" + prefix
|
||||
|
||||
}
|
||||
|
||||
retline += strings.TrimSpace(line[:pos]) + suffix
|
||||
line = strings.TrimSpace(line[pos:])
|
||||
}
|
||||
|
||||
if len(line) > 0 {
|
||||
if len(retline) != 0 {
|
||||
retline += "\n" + prefix
|
||||
}
|
||||
|
||||
retline += line
|
||||
}
|
||||
|
||||
if len(ret) > 0 {
|
||||
ret += "\n"
|
||||
|
||||
if len(retline) > 0 {
|
||||
ret += prefix
|
||||
}
|
||||
}
|
||||
|
||||
ret += retline
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (option *Option) showInHelp() bool {
|
||||
return (option.ShortName != 0 || len(option.LongName) != 0)
|
||||
}
|
||||
|
||||
func (a *alignmentInfo) descriptionStart() int {
|
||||
ret := a.maxLongLen + distanceBetweenOptionAndDescription
|
||||
|
||||
if a.hasShort {
|
||||
ret += 2
|
||||
}
|
||||
|
||||
if a.maxLongLen > 0 {
|
||||
ret += 4
|
||||
}
|
||||
|
||||
if a.hasValueName {
|
||||
ret += 3
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (a *alignmentInfo) updateLen(name string, indent bool) {
|
||||
l := utf8.RuneCountInString(name)
|
||||
|
||||
if indent {
|
||||
l = l + 4
|
||||
}
|
||||
|
||||
if l > a.maxLongLen {
|
||||
a.maxLongLen = l
|
||||
}
|
||||
}
|
||||
|
||||
var group = make(map[string]bool)
|
||||
|
||||
func writeHelpOption(writer *bufio.Writer, option *Option, info alignmentInfo) {
|
||||
line := &bytes.Buffer{}
|
||||
|
||||
if _, ok := group[option.Grouping]; !ok && option.Grouping != "" {
|
||||
writer.WriteString(fmt.Sprintf("\n%s:\n", option.Grouping))
|
||||
group[option.Grouping] = true
|
||||
}
|
||||
|
||||
prefix := paddingBeforeOption
|
||||
|
||||
if info.indent {
|
||||
prefix += 4
|
||||
}
|
||||
|
||||
line.WriteString(strings.Repeat(" ", prefix))
|
||||
|
||||
if option.ShortName != 0 {
|
||||
line.WriteRune(defaultShortOptDelimiter)
|
||||
line.WriteRune(option.ShortName)
|
||||
} else if info.hasShort {
|
||||
line.WriteString(" ")
|
||||
}
|
||||
|
||||
descstart := info.descriptionStart() + paddingBeforeOption
|
||||
|
||||
if len(option.LongName) > 0 {
|
||||
if option.ShortName != 0 {
|
||||
line.WriteString(", ")
|
||||
} else if info.hasShort {
|
||||
line.WriteString(" ")
|
||||
}
|
||||
|
||||
line.WriteString(defaultLongOptDelimiter)
|
||||
line.WriteString(option.LongName)
|
||||
}
|
||||
|
||||
written := line.Len()
|
||||
line.WriteTo(writer)
|
||||
|
||||
if option.Description != "" {
|
||||
dw := descstart - written
|
||||
|
||||
writer.WriteString(strings.Repeat(" ", dw))
|
||||
|
||||
desc := option.Description
|
||||
|
||||
writer.WriteString(wrapText(desc,
|
||||
info.terminalColumns-descstart,
|
||||
strings.Repeat(" ", descstart)))
|
||||
}
|
||||
|
||||
writer.WriteString("\n")
|
||||
}
|
Loading…
Reference in New Issue