mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-11 15:15:00 +00:00
Improve CLI commands (#34973)
Improve help related commands and flags and add tests Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
parent
091b3e696d
commit
4b174e44a8
@ -32,6 +32,7 @@ var (
|
||||
CmdHook = &cli.Command{
|
||||
Name: "hook",
|
||||
Usage: "(internal) Should only be called by Git",
|
||||
Hidden: true, // internal commands shouldn't be visible
|
||||
Description: "Delegate commands to corresponding Git hooks",
|
||||
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
||||
Commands: []*cli.Command{
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
var CmdKeys = &cli.Command{
|
||||
Name: "keys",
|
||||
Usage: "(internal) Should only be called by SSH server",
|
||||
Hidden: true, // internal commands shouldn't not be visible
|
||||
Description: "Queries the Gitea database to get the authorized command for a given ssh key fingerprint",
|
||||
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
||||
Action: runKeys,
|
||||
|
158
cmd/main.go
158
cmd/main.go
@ -6,6 +6,7 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@ -15,26 +16,28 @@ import (
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
// cmdHelp is our own help subcommand with more information
|
||||
// Keep in mind that the "./gitea help"(subcommand) is different from "./gitea --help"(flag), the flag doesn't parse the config or output "DEFAULT CONFIGURATION:" information
|
||||
func cmdHelp() *cli.Command {
|
||||
c := &cli.Command{
|
||||
Name: "help",
|
||||
Aliases: []string{"h"},
|
||||
Usage: "Shows a list of commands or help for one command",
|
||||
ArgsUsage: "[command]",
|
||||
Action: func(ctx context.Context, c *cli.Command) (err error) {
|
||||
lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea
|
||||
targetCmdIdx := 0
|
||||
if c.Name == "help" {
|
||||
targetCmdIdx = 1
|
||||
}
|
||||
if lineage[targetCmdIdx] != lineage[targetCmdIdx].Root() {
|
||||
err = cli.ShowCommandHelp(ctx, lineage[targetCmdIdx+1] /* parent cmd */, lineage[targetCmdIdx].Name /* sub cmd */)
|
||||
} else {
|
||||
err = cli.ShowAppHelp(c)
|
||||
}
|
||||
_, _ = fmt.Fprintf(c.Root().Writer, `
|
||||
var cliHelpPrinterOld = cli.HelpPrinter
|
||||
|
||||
func init() {
|
||||
cli.HelpPrinter = cliHelpPrinterNew
|
||||
}
|
||||
|
||||
// cliHelpPrinterNew helps to print "DEFAULT CONFIGURATION" for the following cases ( "-c" can apper in any position):
|
||||
// * ./gitea -c /dev/null -h
|
||||
// * ./gitea -c help /dev/null help
|
||||
// * ./gitea help -c /dev/null
|
||||
// * ./gitea help -c /dev/null web
|
||||
// * ./gitea help web -c /dev/null
|
||||
// * ./gitea web help -c /dev/null
|
||||
// * ./gitea web -h -c /dev/null
|
||||
func cliHelpPrinterNew(out io.Writer, templ string, data any) {
|
||||
cmd, _ := data.(*cli.Command)
|
||||
if cmd != nil {
|
||||
prepareWorkPathAndCustomConf(cmd)
|
||||
}
|
||||
cliHelpPrinterOld(out, templ, data)
|
||||
if setting.CustomConf != "" {
|
||||
_, _ = fmt.Fprintf(out, `
|
||||
DEFAULT CONFIGURATION:
|
||||
AppPath: %s
|
||||
WorkPath: %s
|
||||
@ -42,77 +45,36 @@ DEFAULT CONFIGURATION:
|
||||
ConfigFile: %s
|
||||
|
||||
`, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf)
|
||||
return err
|
||||
},
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func appGlobalFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
// make the builtin flags at the top
|
||||
cli.HelpFlag,
|
||||
|
||||
// shared configuration flags, they are for global and for each sub-command at the same time
|
||||
// eg: such command is valid: "./gitea --config /tmp/app.ini web --config /tmp/app.ini", while it's discouraged indeed
|
||||
// keep in mind that the short flags like "-C", "-c" and "-w" are globally polluted, they can't be used for sub-commands anymore.
|
||||
&cli.StringFlag{
|
||||
Name: "custom-path",
|
||||
Aliases: []string{"C"},
|
||||
Usage: "Set custom path (defaults to '{WorkPath}/custom')",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "config",
|
||||
Aliases: []string{"c"},
|
||||
Value: setting.CustomConf,
|
||||
Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "work-path",
|
||||
Aliases: []string{"w"},
|
||||
Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func prepareSubcommandWithGlobalFlags(command *cli.Command) {
|
||||
command.Flags = append(append([]cli.Flag{}, appGlobalFlags()...), command.Flags...)
|
||||
command.Action = prepareWorkPathAndCustomConf(command.Action)
|
||||
command.HideHelp = true
|
||||
if command.Name != "help" {
|
||||
command.Commands = append(command.Commands, cmdHelp())
|
||||
}
|
||||
for i := range command.Commands {
|
||||
prepareSubcommandWithGlobalFlags(command.Commands[i])
|
||||
}
|
||||
}
|
||||
|
||||
// prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
|
||||
// It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times
|
||||
func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(context.Context, *cli.Command) error {
|
||||
return func(ctx context.Context, cmd *cli.Command) error {
|
||||
var args setting.ArgWorkPathAndCustomConf
|
||||
// from children to parent, check the global flags
|
||||
for _, curCtx := range cmd.Lineage() {
|
||||
if curCtx.IsSet("work-path") && args.WorkPath == "" {
|
||||
args.WorkPath = curCtx.String("work-path")
|
||||
}
|
||||
if curCtx.IsSet("custom-path") && args.CustomPath == "" {
|
||||
args.CustomPath = curCtx.String("custom-path")
|
||||
}
|
||||
if curCtx.IsSet("config") && args.CustomConf == "" {
|
||||
args.CustomConf = curCtx.String("config")
|
||||
}
|
||||
func prepareSubcommandWithGlobalFlags(originCmd *cli.Command) {
|
||||
originBefore := originCmd.Before
|
||||
originCmd.Before = func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
|
||||
prepareWorkPathAndCustomConf(cmd)
|
||||
if originBefore != nil {
|
||||
return originBefore(ctx, cmd)
|
||||
}
|
||||
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
|
||||
if cmd.Bool("help") || action == nil {
|
||||
// the default behavior of "urfave/cli": "nil action" means "show help"
|
||||
return cmdHelp().Action(ctx, cmd)
|
||||
}
|
||||
return action(ctx, cmd)
|
||||
return ctx, nil
|
||||
}
|
||||
}
|
||||
|
||||
// prepareWorkPathAndCustomConf tries to prepare the work path, custom path and custom config from various inputs:
|
||||
// command line flags, environment variables, config file
|
||||
func prepareWorkPathAndCustomConf(cmd *cli.Command) {
|
||||
var args setting.ArgWorkPathAndCustomConf
|
||||
if cmd.IsSet("work-path") {
|
||||
args.WorkPath = cmd.String("work-path")
|
||||
}
|
||||
if cmd.IsSet("custom-path") {
|
||||
args.CustomPath = cmd.String("custom-path")
|
||||
}
|
||||
if cmd.IsSet("config") {
|
||||
args.CustomConf = cmd.String("config")
|
||||
}
|
||||
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
|
||||
}
|
||||
|
||||
type AppVersion struct {
|
||||
Version string
|
||||
Extra string
|
||||
@ -125,10 +87,29 @@ func NewMainApp(appVer AppVersion) *cli.Command {
|
||||
app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.`
|
||||
app.Version = appVer.Version + appVer.Extra
|
||||
app.EnableShellCompletion = true
|
||||
|
||||
// these sub-commands need to use config file
|
||||
app.Flags = []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "work-path",
|
||||
Aliases: []string{"w"},
|
||||
TakesFile: true,
|
||||
Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "config",
|
||||
Aliases: []string{"c"},
|
||||
TakesFile: true,
|
||||
Value: setting.CustomConf,
|
||||
Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "custom-path",
|
||||
Aliases: []string{"C"},
|
||||
TakesFile: true,
|
||||
Usage: "Set custom path (defaults to '{WorkPath}/custom')",
|
||||
},
|
||||
}
|
||||
// these sub-commands need to use a config file
|
||||
subCmdWithConfig := []*cli.Command{
|
||||
cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
|
||||
CmdWeb,
|
||||
CmdServ,
|
||||
CmdHook,
|
||||
@ -156,9 +137,6 @@ func NewMainApp(appVer AppVersion) *cli.Command {
|
||||
// but not sure whether it would break Windows users who used to double-click the EXE to run.
|
||||
app.DefaultCommand = CmdWeb.Name
|
||||
|
||||
app.Flags = append(app.Flags, cli.VersionFlag)
|
||||
app.Flags = append(app.Flags, appGlobalFlags()...)
|
||||
app.HideHelp = true // use our own help action to show helps (with more information like default config)
|
||||
app.Before = PrepareConsoleLoggerLevel(log.INFO)
|
||||
for i := range subCmdWithConfig {
|
||||
prepareSubcommandWithGlobalFlags(subCmdWithConfig[i])
|
||||
|
@ -74,12 +74,56 @@ func TestCliCmd(t *testing.T) {
|
||||
cmd string
|
||||
exp string
|
||||
}{
|
||||
// main command help
|
||||
// help commands
|
||||
{
|
||||
cmd: "./gitea -h",
|
||||
exp: "DEFAULT CONFIGURATION:",
|
||||
},
|
||||
{
|
||||
cmd: "./gitea help",
|
||||
exp: "DEFAULT CONFIGURATION:",
|
||||
},
|
||||
|
||||
{
|
||||
cmd: "./gitea -c /dev/null -h",
|
||||
exp: "ConfigFile: /dev/null",
|
||||
},
|
||||
|
||||
{
|
||||
cmd: "./gitea -c /dev/null help",
|
||||
exp: "ConfigFile: /dev/null",
|
||||
},
|
||||
{
|
||||
cmd: "./gitea help -c /dev/null",
|
||||
exp: "ConfigFile: /dev/null",
|
||||
},
|
||||
|
||||
{
|
||||
cmd: "./gitea -c /dev/null test-cmd -h",
|
||||
exp: "ConfigFile: /dev/null",
|
||||
},
|
||||
{
|
||||
cmd: "./gitea test-cmd -c /dev/null -h",
|
||||
exp: "ConfigFile: /dev/null",
|
||||
},
|
||||
{
|
||||
cmd: "./gitea test-cmd -h -c /dev/null",
|
||||
exp: "ConfigFile: /dev/null",
|
||||
},
|
||||
|
||||
{
|
||||
cmd: "./gitea -c /dev/null test-cmd help",
|
||||
exp: "ConfigFile: /dev/null",
|
||||
},
|
||||
{
|
||||
cmd: "./gitea test-cmd -c /dev/null help",
|
||||
exp: "ConfigFile: /dev/null",
|
||||
},
|
||||
{
|
||||
cmd: "./gitea test-cmd help -c /dev/null",
|
||||
exp: "ConfigFile: /dev/null",
|
||||
},
|
||||
|
||||
// parse paths
|
||||
{
|
||||
cmd: "./gitea test-cmd",
|
||||
|
@ -41,6 +41,7 @@ var CmdServ = &cli.Command{
|
||||
Name: "serv",
|
||||
Usage: "(internal) Should only be called by SSH shell",
|
||||
Description: "Serv provides access auth for repositories",
|
||||
Hidden: true, // Internal commands shouldn't be visible in help
|
||||
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
||||
Action: runServ,
|
||||
Flags: []cli.Flag{
|
||||
|
@ -1,17 +0,0 @@
|
||||
Bash and Zsh completion
|
||||
=======================
|
||||
|
||||
From within the gitea root run:
|
||||
|
||||
```bash
|
||||
source contrib/autocompletion/bash_autocomplete
|
||||
```
|
||||
|
||||
or for zsh run:
|
||||
|
||||
```bash
|
||||
source contrib/autocompletion/zsh_autocomplete
|
||||
```
|
||||
|
||||
These scripts will check if gitea is on the path and if so add autocompletion for `gitea`. Or if not autocompletion will work for `./gitea`.
|
||||
If gitea has been installed as a different program pass in the `PROG` environment variable to set the correct program name.
|
@ -1,30 +0,0 @@
|
||||
#! /bin/bash
|
||||
# Heavily inspired by https://github.com/urfave/cli
|
||||
|
||||
_cli_bash_autocomplete() {
|
||||
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
|
||||
local cur opts base
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
if [[ "$cur" == "-"* ]]; then
|
||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
|
||||
else
|
||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
||||
fi
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -z "$PROG" ] && [ ! "$(command -v gitea &> /dev/null)" ] ; then
|
||||
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete gitea
|
||||
elif [ -z "$PROG" ]; then
|
||||
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete ./gitea
|
||||
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete "$PWD/gitea"
|
||||
else
|
||||
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete "$PROG"
|
||||
unset PROG
|
||||
fi
|
||||
|
||||
|
||||
|
@ -1,30 +0,0 @@
|
||||
#compdef ${PROG:=gitea}
|
||||
|
||||
|
||||
# Heavily inspired by https://github.com/urfave/cli
|
||||
|
||||
_cli_zsh_autocomplete() {
|
||||
|
||||
local -a opts
|
||||
local cur
|
||||
cur=${words[-1]}
|
||||
if [[ "$cur" == "-"* ]]; then
|
||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
|
||||
else
|
||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}")
|
||||
fi
|
||||
|
||||
if [[ "${opts[1]}" != "" ]]; then
|
||||
_describe 'values' opts
|
||||
else
|
||||
_files
|
||||
fi
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if [ -z $PROG ] ; then
|
||||
compdef _cli_zsh_autocomplete gitea
|
||||
else
|
||||
compdef _cli_zsh_autocomplete $(basename $PROG)
|
||||
fi
|
Loading…
Reference in New Issue
Block a user