This commit is contained in:
RPRX 2020-11-25 19:01:53 +08:00
parent 47d23e9972
commit c7f7c08ead
711 changed files with 82154 additions and 2 deletions

View file

@ -0,0 +1,122 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package base defines shared basic pieces of the commands,
// in particular logging and the Command structure.
package base
import (
"flag"
"fmt"
"os"
"strings"
"sync"
)
// A Command is an implementation of a xray command
// like xray run or xray version.
type Command struct {
// Run runs the command.
// The args are the arguments after the command name.
Run func(cmd *Command, args []string)
// UsageLine is the one-line usage message.
// The words between "go" and the first flag or argument in the line are taken to be the command name.
UsageLine string
// Short is the short description shown in the 'go help' output.
Short string
// Long is the long message shown in the 'go help <this-command>' output.
Long string
// Flag is a set of flags specific to this command.
Flag flag.FlagSet
// CustomFlags indicates that the command will do its own
// flag parsing.
CustomFlags bool
// Commands lists the available commands and help topics.
// The order here is the order in which they are printed by 'go help'.
// Note that subcommands are in general best avoided.
Commands []*Command
}
// LongName returns the command's long name: all the words in the usage line between "go" and a flag or argument,
func (c *Command) LongName() string {
name := c.UsageLine
if i := strings.Index(name, " ["); i >= 0 {
name = name[:i]
}
if name == CommandEnv.Exec {
return ""
}
return strings.TrimPrefix(name, CommandEnv.Exec+" ")
}
// Name returns the command's short name: the last word in the usage line before a flag or argument.
func (c *Command) Name() string {
name := c.LongName()
if i := strings.LastIndex(name, " "); i >= 0 {
name = name[i+1:]
}
return name
}
// Usage prints usage of the Command
func (c *Command) Usage() {
fmt.Fprintf(os.Stderr, "usage: %s\n", c.UsageLine)
fmt.Fprintf(os.Stderr, "Run 'xray help %s' for details.\n", c.LongName())
SetExitStatus(2)
Exit()
}
// Runnable reports whether the command can be run; otherwise
// it is a documentation pseudo-command such as importpath.
func (c *Command) Runnable() bool {
return c.Run != nil
}
// Exit exits with code set with SetExitStatus()
func Exit() {
os.Exit(exitStatus)
}
// Fatalf logs error and exit with code 1
func Fatalf(format string, args ...interface{}) {
Errorf(format, args...)
Exit()
}
// Errorf logs error and set exit status to 1, but not exit
func Errorf(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format, args...)
fmt.Fprintln(os.Stderr)
SetExitStatus(1)
}
// ExitIfErrors exits if current status is not zero
func ExitIfErrors() {
if exitStatus != 0 {
Exit()
}
}
var exitStatus = 0
var exitMu sync.Mutex
// SetExitStatus set exit status code
func SetExitStatus(n int) {
exitMu.Lock()
if exitStatus < n {
exitStatus = n
}
exitMu.Unlock()
}
// GetExitStatus get exit status code
func GetExitStatus() int {
return exitStatus
}

23
main/commands/base/env.go Normal file
View file

@ -0,0 +1,23 @@
package base
import (
"os"
"path"
)
// CommandEnvHolder is a struct holds the environment info of commands
type CommandEnvHolder struct {
Exec string
}
// CommandEnv holds the environment info of commands
var CommandEnv CommandEnvHolder
func init() {
exec, err := os.Executable()
if err != nil {
return
}
CommandEnv.Exec = path.Base(exec)
CommandEnv.Exec = "xray"
}

View file

@ -0,0 +1,88 @@
package base
import (
"flag"
"fmt"
"os"
"sort"
"strings"
)
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// copied from "github.com/golang/go/main.go"
// Execute excute the commands
func Execute() {
buildCommandsText(RootCommand)
flag.Parse()
args := flag.Args()
if len(args) < 1 {
PrintUsage(os.Stderr, RootCommand)
return
}
cmdName := args[0] // for error messages
if args[0] == "help" {
Help(os.Stdout, args[1:])
return
}
BigCmdLoop:
for bigCmd := RootCommand; ; {
for _, cmd := range bigCmd.Commands {
if cmd.Name() != args[0] {
continue
}
if len(cmd.Commands) > 0 {
// test sub commands
bigCmd = cmd
args = args[1:]
if len(args) == 0 {
PrintUsage(os.Stderr, bigCmd)
SetExitStatus(2)
Exit()
}
if args[0] == "help" {
// Accept 'go mod help' and 'go mod help foo' for 'go help mod' and 'go help mod foo'.
Help(os.Stdout, append(strings.Split(cmdName, " "), args[1:]...))
return
}
cmdName += " " + args[0]
continue BigCmdLoop
}
if !cmd.Runnable() {
continue
}
cmd.Flag.Usage = func() { cmd.Usage() }
if cmd.CustomFlags {
args = args[1:]
} else {
cmd.Flag.Parse(args[1:])
args = cmd.Flag.Args()
}
cmd.Run(cmd, args)
Exit()
return
}
helpArg := ""
if i := strings.LastIndex(cmdName, " "); i >= 0 {
helpArg = " " + cmdName[:i]
}
fmt.Fprintf(os.Stderr, "%s %s: unknown command\nRun '%s help%s' for usage.\n", CommandEnv.Exec, cmdName, CommandEnv.Exec, helpArg)
SetExitStatus(2)
Exit()
}
}
// Sort sorts the commands
func Sort() {
sort.Slice(RootCommand.Commands, func(i, j int) bool {
return SortLessFunc(RootCommand.Commands[i], RootCommand.Commands[j])
})
}
// SortLessFunc used for sort commands list, can be override from outside
var SortLessFunc = func(i, j *Command) bool {
return i.Name() < j.Name()
}

157
main/commands/base/help.go Normal file
View file

@ -0,0 +1,157 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package base
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"strings"
"text/template"
"unicode"
"unicode/utf8"
)
// Help implements the 'help' command.
func Help(w io.Writer, args []string) {
cmd := RootCommand
Args:
for i, arg := range args {
for _, sub := range cmd.Commands {
if sub.Name() == arg {
cmd = sub
continue Args
}
}
// helpSuccess is the help command using as many args as possible that would succeed.
helpSuccess := CommandEnv.Exec + " help"
if i > 0 {
helpSuccess += " " + strings.Join(args[:i], " ")
}
fmt.Fprintf(os.Stderr, "%s help %s: unknown help topic. Run '%s'.\n", CommandEnv.Exec, strings.Join(args, " "), helpSuccess)
SetExitStatus(2) // failed at 'xray help cmd'
Exit()
}
if len(cmd.Commands) > 0 {
PrintUsage(os.Stdout, cmd)
} else {
tmpl(os.Stdout, helpTemplate, makeTmplData(cmd))
}
}
var usageTemplate = `{{.Long | trim}}
Usage:
{{.Exec}} <command> [arguments]
The commands are:
{{range .Commands}}{{if and (ne .Short "") (or (.Runnable) .Commands)}}
{{.Name | printf "%-12s"}} {{.Short}}{{end}}{{end}}
Use "{{.Exec}} help{{with .LongName}} {{.}}{{end}} <command>" for more information about a command.
`
// APPEND FOLLOWING TO 'usageTemplate' IF YOU WANT DOC,
// A DOC TOPIC IS JUST A COMMAND NOT RUNNABLE:
//
// {{if eq (.UsageLine) (.Exec)}}
// Additional help topics:
// {{range .Commands}}{{if and (not .Runnable) (not .Commands)}}
// {{.Name | printf "%-15s"}} {{.Short}}{{end}}{{end}}
//
// Use "{{.Exec}} help{{with .LongName}} {{.}}{{end}} <topic>" for more information about that topic.
// {{end}}
var helpTemplate = `{{if .Runnable}}usage: {{.UsageLine}}
{{end}}{{.Long | trim}}
`
// An errWriter wraps a writer, recording whether a write error occurred.
type errWriter struct {
w io.Writer
err error
}
func (w *errWriter) Write(b []byte) (int, error) {
n, err := w.w.Write(b)
if err != nil {
w.err = err
}
return n, err
}
// tmpl executes the given template text on data, writing the result to w.
func tmpl(w io.Writer, text string, data interface{}) {
t := template.New("top")
t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize})
template.Must(t.Parse(text))
ew := &errWriter{w: w}
err := t.Execute(ew, data)
if ew.err != nil {
// I/O error writing. Ignore write on closed pipe.
if strings.Contains(ew.err.Error(), "pipe") {
SetExitStatus(1)
Exit()
}
Fatalf("writing output: %v", ew.err)
}
if err != nil {
panic(err)
}
}
func capitalize(s string) string {
if s == "" {
return s
}
r, n := utf8.DecodeRuneInString(s)
return string(unicode.ToTitle(r)) + s[n:]
}
// PrintUsage prints usage of cmd to w
func PrintUsage(w io.Writer, cmd *Command) {
bw := bufio.NewWriter(w)
tmpl(bw, usageTemplate, makeTmplData(cmd))
bw.Flush()
}
// buildCommandsText build text of command and its children as template
func buildCommandsText(cmd *Command) {
buildCommandText(cmd)
for _, cmd := range cmd.Commands {
buildCommandsText(cmd)
}
}
// buildCommandText build command text as template
func buildCommandText(cmd *Command) {
cmd.UsageLine = buildText(cmd.UsageLine, makeTmplData(cmd))
cmd.Short = buildText(cmd.Short, makeTmplData(cmd))
cmd.Long = buildText(cmd.Long, makeTmplData(cmd))
}
func buildText(text string, data interface{}) string {
buf := bytes.NewBuffer([]byte{})
tmpl(buf, text, data)
return buf.String()
}
type tmplData struct {
*Command
*CommandEnvHolder
}
func makeTmplData(cmd *Command) tmplData {
return tmplData{
Command: cmd,
CommandEnvHolder: &CommandEnv,
}
}

View file

@ -0,0 +1,16 @@
package base
// RootCommand is the root command of all commands
var RootCommand *Command
func init() {
RootCommand = &Command{
UsageLine: CommandEnv.Exec,
Long: "The root command",
}
}
// RegisterCommand register a command to RootCommand
func RegisterCommand(cmd *Command) {
RootCommand.Commands = append(RootCommand.Commands, cmd)
}