mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-10 21:53:57 +08:00
chore(deps): upgrade urfave/cli from v1 to v3
Migrate from urfave/cli v1 (maintenance mode) to v3 to benefit from active development, improved features, and long-term support. Signed-off-by: lifubang <lifubang@acmcoder.com>
This commit is contained in:
+49
-46
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -11,13 +12,13 @@ import (
|
||||
"github.com/moby/sys/userns"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
)
|
||||
|
||||
var checkpointCommand = cli.Command{
|
||||
var checkpointCommand = &cli.Command{
|
||||
Name: "checkpoint",
|
||||
Usage: "checkpoint a running container",
|
||||
ArgsUsage: `<container-id>
|
||||
@@ -25,27 +26,29 @@ var checkpointCommand = cli.Command{
|
||||
Where "<container-id>" is the name for the instance of the container to be
|
||||
checkpointed.`,
|
||||
Description: `The checkpoint command saves the state of the container instance.`,
|
||||
// Disable comma as separator for slice flags.
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "image-path", Value: "", Usage: "path for saving criu image files"},
|
||||
cli.StringFlag{Name: "work-path", Value: "", Usage: "path for saving work files and logs"},
|
||||
cli.StringFlag{Name: "parent-path", Value: "", Usage: "path for previous criu image files in pre-dump"},
|
||||
cli.BoolFlag{Name: "leave-running", Usage: "leave the process running after checkpointing"},
|
||||
cli.BoolFlag{Name: "tcp-established", Usage: "allow open tcp connections"},
|
||||
cli.BoolFlag{Name: "tcp-skip-in-flight", Usage: "skip in-flight tcp connections"},
|
||||
cli.BoolFlag{Name: "link-remap", Usage: "allow one to link unlinked files back when possible"},
|
||||
cli.BoolFlag{Name: "ext-unix-sk", Usage: "allow external unix sockets"},
|
||||
cli.BoolFlag{Name: "shell-job", Usage: "allow shell jobs"},
|
||||
cli.BoolFlag{Name: "lazy-pages", Usage: "use userfaultfd to lazily restore memory pages"},
|
||||
cli.IntFlag{Name: "status-fd", Value: -1, Usage: "criu writes \\0 to this FD once lazy-pages is ready"},
|
||||
cli.StringFlag{Name: "page-server", Value: "", Usage: "ADDRESS:PORT of the page server"},
|
||||
cli.BoolFlag{Name: "file-locks", Usage: "handle file locks, for safety"},
|
||||
cli.BoolFlag{Name: "pre-dump", Usage: "dump container's memory information only, leave the container running after this"},
|
||||
cli.StringFlag{Name: "manage-cgroups-mode", Value: "", Usage: "cgroups mode: soft|full|strict|ignore (default: soft)"},
|
||||
cli.StringSliceFlag{Name: "empty-ns", Usage: "create a namespace, but don't restore its properties"},
|
||||
cli.BoolFlag{Name: "auto-dedup", Usage: "enable auto deduplication of memory images"},
|
||||
&cli.StringFlag{Name: "image-path", Value: "", Usage: "path for saving criu image files"},
|
||||
&cli.StringFlag{Name: "work-path", Value: "", Usage: "path for saving work files and logs"},
|
||||
&cli.StringFlag{Name: "parent-path", Value: "", Usage: "path for previous criu image files in pre-dump"},
|
||||
&cli.BoolFlag{Name: "leave-running", Usage: "leave the process running after checkpointing"},
|
||||
&cli.BoolFlag{Name: "tcp-established", Usage: "allow open tcp connections"},
|
||||
&cli.BoolFlag{Name: "tcp-skip-in-flight", Usage: "skip in-flight tcp connections"},
|
||||
&cli.BoolFlag{Name: "link-remap", Usage: "allow one to link unlinked files back when possible"},
|
||||
&cli.BoolFlag{Name: "ext-unix-sk", Usage: "allow external unix sockets"},
|
||||
&cli.BoolFlag{Name: "shell-job", Usage: "allow shell jobs"},
|
||||
&cli.BoolFlag{Name: "lazy-pages", Usage: "use userfaultfd to lazily restore memory pages"},
|
||||
&cli.IntFlag{Name: "status-fd", Value: -1, Usage: "criu writes \\0 to this FD once lazy-pages is ready"},
|
||||
&cli.StringFlag{Name: "page-server", Value: "", Usage: "ADDRESS:PORT of the page server"},
|
||||
&cli.BoolFlag{Name: "file-locks", Usage: "handle file locks, for safety"},
|
||||
&cli.BoolFlag{Name: "pre-dump", Usage: "dump container's memory information only, leave the container running after this"},
|
||||
&cli.StringFlag{Name: "manage-cgroups-mode", Value: "", Usage: "cgroups mode: soft|full|strict|ignore (default: soft)"},
|
||||
&cli.StringSliceFlag{Name: "empty-ns", Usage: "create a namespace, but don't restore its properties"},
|
||||
&cli.BoolFlag{Name: "auto-dedup", Usage: "enable auto deduplication of memory images"},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
// XXX: Currently this is untested with rootless containers.
|
||||
@@ -53,7 +56,7 @@ checkpointed.`,
|
||||
logrus.Warn("runc checkpoint is untested with rootless containers")
|
||||
}
|
||||
|
||||
container, err := getContainer(context)
|
||||
container, err := getContainer(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -64,7 +67,7 @@ checkpointed.`,
|
||||
if status == libcontainer.Created || status == libcontainer.Stopped {
|
||||
return fmt.Errorf("Container cannot be checkpointed in %s state", status.String())
|
||||
}
|
||||
options, err := criuOptions(context)
|
||||
options, err := criuOptions(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -80,8 +83,8 @@ checkpointed.`,
|
||||
},
|
||||
}
|
||||
|
||||
func prepareImagePaths(context *cli.Context) (string, string, error) {
|
||||
imagePath := context.String("image-path")
|
||||
func prepareImagePaths(cmd *cli.Command) (string, string, error) {
|
||||
imagePath := cmd.String("image-path")
|
||||
if imagePath == "" {
|
||||
imagePath = getDefaultImagePath()
|
||||
}
|
||||
@@ -90,7 +93,7 @@ func prepareImagePaths(context *cli.Context) (string, string, error) {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
parentPath := context.String("parent-path")
|
||||
parentPath := cmd.String("parent-path")
|
||||
if parentPath == "" {
|
||||
return imagePath, parentPath, nil
|
||||
}
|
||||
@@ -112,35 +115,35 @@ func prepareImagePaths(context *cli.Context) (string, string, error) {
|
||||
return imagePath, parentPath, nil
|
||||
}
|
||||
|
||||
func criuOptions(context *cli.Context) (*libcontainer.CriuOpts, error) {
|
||||
imagePath, parentPath, err := prepareImagePaths(context)
|
||||
func criuOptions(cmd *cli.Command) (*libcontainer.CriuOpts, error) {
|
||||
imagePath, parentPath, err := prepareImagePaths(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts := &libcontainer.CriuOpts{
|
||||
ImagesDirectory: imagePath,
|
||||
WorkDirectory: context.String("work-path"),
|
||||
WorkDirectory: cmd.String("work-path"),
|
||||
ParentImage: parentPath,
|
||||
LeaveRunning: context.Bool("leave-running"),
|
||||
TcpEstablished: context.Bool("tcp-established"),
|
||||
TcpSkipInFlight: context.Bool("tcp-skip-in-flight"),
|
||||
LinkRemap: context.Bool("link-remap"),
|
||||
ExternalUnixConnections: context.Bool("ext-unix-sk"),
|
||||
ShellJob: context.Bool("shell-job"),
|
||||
FileLocks: context.Bool("file-locks"),
|
||||
PreDump: context.Bool("pre-dump"),
|
||||
AutoDedup: context.Bool("auto-dedup"),
|
||||
LazyPages: context.Bool("lazy-pages"),
|
||||
StatusFd: context.Int("status-fd"),
|
||||
LsmProfile: context.String("lsm-profile"),
|
||||
LsmMountContext: context.String("lsm-mount-context"),
|
||||
ManageCgroupsMode: context.String("manage-cgroups-mode"),
|
||||
LeaveRunning: cmd.Bool("leave-running"),
|
||||
TcpEstablished: cmd.Bool("tcp-established"),
|
||||
TcpSkipInFlight: cmd.Bool("tcp-skip-in-flight"),
|
||||
LinkRemap: cmd.Bool("link-remap"),
|
||||
ExternalUnixConnections: cmd.Bool("ext-unix-sk"),
|
||||
ShellJob: cmd.Bool("shell-job"),
|
||||
FileLocks: cmd.Bool("file-locks"),
|
||||
PreDump: cmd.Bool("pre-dump"),
|
||||
AutoDedup: cmd.Bool("auto-dedup"),
|
||||
LazyPages: cmd.Bool("lazy-pages"),
|
||||
StatusFd: cmd.Int("status-fd"),
|
||||
LsmProfile: cmd.String("lsm-profile"),
|
||||
LsmMountContext: cmd.String("lsm-mount-context"),
|
||||
ManageCgroupsMode: cmd.String("manage-cgroups-mode"),
|
||||
}
|
||||
|
||||
// CRIU options below may or may not be set.
|
||||
|
||||
if psOpt := context.String("page-server"); psOpt != "" {
|
||||
if psOpt := cmd.String("page-server"); psOpt != "" {
|
||||
address, port, err := net.SplitHostPort(psOpt)
|
||||
|
||||
if err != nil || address == "" || port == "" {
|
||||
@@ -159,12 +162,12 @@ func criuOptions(context *cli.Context) (*libcontainer.CriuOpts, error) {
|
||||
// runc doesn't manage network devices and their configuration.
|
||||
nsmask := unix.CLONE_NEWNET
|
||||
|
||||
if context.IsSet("empty-ns") {
|
||||
if cmd.IsSet("empty-ns") {
|
||||
namespaceMapping := map[specs.LinuxNamespaceType]int{
|
||||
specs.NetworkNamespace: unix.CLONE_NEWNET,
|
||||
}
|
||||
|
||||
for _, ns := range context.StringSlice("empty-ns") {
|
||||
for _, ns := range cmd.StringSlice("empty-ns") {
|
||||
f, exists := namespaceMapping[specs.LinuxNamespaceType(ns)]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("namespace %q is not supported", ns)
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var createCommand = cli.Command{
|
||||
var createCommand = &cli.Command{
|
||||
Name: "create",
|
||||
Usage: "create a container",
|
||||
ArgsUsage: `<container-id>
|
||||
@@ -23,44 +24,47 @@ The specification file includes an args parameter. The args parameter is used
|
||||
to specify command(s) that get run when the container is started. To change the
|
||||
command(s) that get executed on start, edit the args parameter of the spec. See
|
||||
"runc spec --help" for more explanation.`,
|
||||
// Disable comma as separator for slice flags.
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "bundle, b",
|
||||
Value: "",
|
||||
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
||||
&cli.StringFlag{
|
||||
Name: "bundle",
|
||||
Aliases: []string{"b"},
|
||||
Value: "",
|
||||
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "console-socket",
|
||||
Value: "",
|
||||
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "pidfd-socket",
|
||||
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the init process",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "pid-file",
|
||||
Value: "",
|
||||
Usage: "specify the file to write the process id to",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "no-pivot",
|
||||
Usage: "do not use pivot root to jail process inside rootfs. This should be used whenever the rootfs is on top of a ramdisk",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "no-new-keyring",
|
||||
Usage: "do not create a new session keyring for the container. This will cause the container to inherit the calling processes session key",
|
||||
},
|
||||
cli.IntFlag{
|
||||
&cli.IntFlag{
|
||||
Name: "preserve-fds",
|
||||
Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
status, err := startContainer(context, CT_ACT_CREATE, nil)
|
||||
status, err := startContainer(cmd, CT_ACT_CREATE, nil)
|
||||
if err == nil {
|
||||
// exit with the container's exit status so any external supervisor
|
||||
// is notified of the exit with the correct exit status.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -8,7 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
@@ -24,7 +25,7 @@ func killContainer(container *libcontainer.Container) error {
|
||||
return errors.New("container init still running")
|
||||
}
|
||||
|
||||
var deleteCommand = cli.Command{
|
||||
var deleteCommand = &cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "delete any resources held by the container often used with detached container",
|
||||
ArgsUsage: `<container-id>
|
||||
@@ -37,25 +38,28 @@ status of "ubuntu01" as "stopped" the following will delete resources held for
|
||||
"ubuntu01" removing "ubuntu01" from the runc list of containers:
|
||||
|
||||
# runc delete ubuntu01`,
|
||||
// Disable comma as separator for slice flags.
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "Forcibly deletes the container if it is still running (uses SIGKILL)",
|
||||
&cli.BoolFlag{
|
||||
Name: "force",
|
||||
Aliases: []string{"f"},
|
||||
Usage: "Forcibly deletes the container if it is still running (uses SIGKILL)",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id := context.Args().First()
|
||||
force := context.Bool("force")
|
||||
container, err := getContainer(context)
|
||||
id := cmd.Args().First()
|
||||
force := cmd.Bool("force")
|
||||
container, err := getContainer(cmd)
|
||||
if err != nil {
|
||||
if errors.Is(err, libcontainer.ErrNotExist) {
|
||||
// if there was an aborted start or something of the sort then the container's directory could exist but
|
||||
// libcontainer does not see it because the state.json file inside that directory was never created.
|
||||
path := filepath.Join(context.GlobalString("root"), id)
|
||||
path := filepath.Join(cmd.String("root"), id)
|
||||
if e := os.RemoveAll(path); e != nil {
|
||||
fmt.Fprintf(os.Stderr, "remove %s: %v\n", path, e)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -14,10 +15,10 @@ import (
|
||||
"github.com/opencontainers/runc/types"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var eventsCommand = cli.Command{
|
||||
var eventsCommand = &cli.Command{
|
||||
Name: "events",
|
||||
Usage: "display container events such as OOM notifications, cpu, memory, and IO usage statistics",
|
||||
ArgsUsage: `<container-id>
|
||||
@@ -25,19 +26,21 @@ var eventsCommand = cli.Command{
|
||||
Where "<container-id>" is the name for the instance of the container.`,
|
||||
Description: `The events command displays information about the container. By default the
|
||||
information is displayed once every 5 seconds.`,
|
||||
// Disable comma as separator for slice flags.
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
cli.DurationFlag{Name: "interval", Value: 5 * time.Second, Usage: "set the stats collection interval"},
|
||||
cli.BoolFlag{Name: "stats", Usage: "display the container's stats then exit"},
|
||||
&cli.DurationFlag{Name: "interval", Value: 5 * time.Second, Usage: "set the stats collection interval"},
|
||||
&cli.BoolFlag{Name: "stats", Usage: "display the container's stats then exit"},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
container, err := getContainer(context)
|
||||
container, err := getContainer(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
duration := context.Duration("interval")
|
||||
duration := cmd.Duration("interval")
|
||||
if duration <= 0 {
|
||||
return errors.New("duration interval must be greater than 0")
|
||||
}
|
||||
@@ -61,7 +64,7 @@ information is displayed once every 5 seconds.`,
|
||||
}
|
||||
}
|
||||
})
|
||||
if context.Bool("stats") {
|
||||
if cmd.Bool("stats") {
|
||||
s, err := container.Stats()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -72,7 +75,7 @@ information is displayed once every 5 seconds.`,
|
||||
return nil
|
||||
}
|
||||
go func() {
|
||||
for range time.Tick(context.Duration("interval")) {
|
||||
for range time.Tick(cmd.Duration("interval")) {
|
||||
s, err := container.Stats()
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -11,10 +12,10 @@ import (
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
"github.com/opencontainers/runc/libcontainer/utils"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var execCommand = cli.Command{
|
||||
var execCommand = &cli.Command{
|
||||
Name: "exec",
|
||||
Usage: "execute new process inside the container",
|
||||
ArgsUsage: `<container-id> <command> [command options] || -p process.json <container-id>
|
||||
@@ -28,93 +29,105 @@ For example, if the container is configured to run the linux ps command the
|
||||
following will output a list of processes running in the container:
|
||||
|
||||
# runc exec <container-id> ps`,
|
||||
// Stop parsing flags after the first positional argument (command).
|
||||
// This allows passing flags like -c to the command being executed.
|
||||
StopOnNthArg: mkPtr(1),
|
||||
// Disable comma as separator for slice flags.
|
||||
// This allows cgroup controller lists like "cpu,cpuacct:subcpu".
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "console-socket",
|
||||
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "pidfd-socket",
|
||||
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the exec process",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "cwd",
|
||||
Usage: "current working directory in the container",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "env, e",
|
||||
Usage: "set environment variables",
|
||||
&cli.StringSliceFlag{
|
||||
Name: "env",
|
||||
Aliases: []string{"e"},
|
||||
Usage: "set environment variables",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "tty, t",
|
||||
Usage: "allocate a pseudo-TTY",
|
||||
&cli.BoolFlag{
|
||||
Name: "tty",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "allocate a pseudo-TTY",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "user, u",
|
||||
Usage: "UID (format: <uid>[:<gid>])",
|
||||
&cli.StringFlag{
|
||||
Name: "user",
|
||||
Aliases: []string{"u"},
|
||||
Usage: "UID (format: <uid>[:<gid>])",
|
||||
},
|
||||
cli.Int64SliceFlag{
|
||||
Name: "additional-gids, g",
|
||||
Usage: "additional gids",
|
||||
&cli.Int64SliceFlag{
|
||||
Name: "additional-gids",
|
||||
Aliases: []string{"g"},
|
||||
Usage: "additional gids",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "process, p",
|
||||
Usage: "path to the process.json",
|
||||
&cli.StringFlag{
|
||||
Name: "process",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "path to the process.json",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "detach,d",
|
||||
Usage: "detach from the container's process",
|
||||
&cli.BoolFlag{
|
||||
Name: "detach",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "detach from the container's process",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "pid-file",
|
||||
Value: "",
|
||||
Usage: "specify the file to write the process id to",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "process-label",
|
||||
Usage: "set the asm process label for the process commonly used with selinux",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "apparmor",
|
||||
Usage: "set the apparmor profile for the process",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "no-new-privs",
|
||||
Usage: "set the no new privileges value for the process",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "cap, c",
|
||||
Value: &cli.StringSlice{},
|
||||
Usage: "add a capability to the bounding set for the process",
|
||||
&cli.StringSliceFlag{
|
||||
Name: "cap",
|
||||
Aliases: []string{"c"},
|
||||
Value: []string{},
|
||||
Usage: "add a capability to the bounding set for the process",
|
||||
},
|
||||
cli.IntFlag{
|
||||
&cli.IntFlag{
|
||||
Name: "preserve-fds",
|
||||
Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
&cli.StringSliceFlag{
|
||||
Name: "cgroup",
|
||||
Usage: "run the process in an (existing) sub-cgroup(s). Format is [<controller>:]<cgroup>.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "ignore-paused",
|
||||
Usage: "allow exec in a paused container",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
if err := checkArgs(context, 1, minArgs); err != nil {
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
if err := checkArgs(cmd, 1, minArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := revisePidFile(context); err != nil {
|
||||
if err := revisePidFile(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
status, err := execProcess(context)
|
||||
status, err := execProcess(cmd)
|
||||
if err == nil {
|
||||
os.Exit(status)
|
||||
}
|
||||
fatalWithCode(fmt.Errorf("exec failed: %w", err), 255)
|
||||
return nil // to satisfy the linter
|
||||
},
|
||||
SkipArgReorder: true,
|
||||
}
|
||||
|
||||
// getSubCgroupPaths parses --cgroup arguments, which can either be
|
||||
@@ -152,8 +165,8 @@ func getSubCgroupPaths(args []string) (map[string]string, error) {
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func execProcess(context *cli.Context) (int, error) {
|
||||
container, err := getContainer(context)
|
||||
func execProcess(cmd *cli.Command) (int, error) {
|
||||
container, err := getContainer(cmd)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
@@ -164,15 +177,15 @@ func execProcess(context *cli.Context) (int, error) {
|
||||
if status == libcontainer.Stopped {
|
||||
return -1, errors.New("cannot exec in a stopped container")
|
||||
}
|
||||
if status == libcontainer.Paused && !context.Bool("ignore-paused") {
|
||||
if status == libcontainer.Paused && !cmd.Bool("ignore-paused") {
|
||||
return -1, errors.New("cannot exec in a paused container (use --ignore-paused to override)")
|
||||
}
|
||||
p, err := getProcess(context, container)
|
||||
p, err := getProcess(cmd, container)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
cgPaths, err := getSubCgroupPaths(context.StringSlice("cgroup"))
|
||||
cgPaths, err := getSubCgroupPaths(cmd.StringSlice("cgroup"))
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
@@ -181,20 +194,20 @@ func execProcess(context *cli.Context) (int, error) {
|
||||
enableSubreaper: false,
|
||||
shouldDestroy: false,
|
||||
container: container,
|
||||
consoleSocket: context.String("console-socket"),
|
||||
pidfdSocket: context.String("pidfd-socket"),
|
||||
detach: context.Bool("detach"),
|
||||
pidFile: context.String("pid-file"),
|
||||
consoleSocket: cmd.String("console-socket"),
|
||||
pidfdSocket: cmd.String("pidfd-socket"),
|
||||
detach: cmd.Bool("detach"),
|
||||
pidFile: cmd.String("pid-file"),
|
||||
action: CT_ACT_RUN,
|
||||
init: false,
|
||||
preserveFDs: context.Int("preserve-fds"),
|
||||
preserveFDs: cmd.Int("preserve-fds"),
|
||||
subCgroupPaths: cgPaths,
|
||||
}
|
||||
return r.run(p)
|
||||
}
|
||||
|
||||
func getProcess(context *cli.Context, c *libcontainer.Container) (*specs.Process, error) {
|
||||
if path := context.String("process"); path != "" {
|
||||
func getProcess(cmd *cli.Command, c *libcontainer.Container) (*specs.Process, error) {
|
||||
if path := cmd.String("process"); path != "" {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -219,22 +232,22 @@ func getProcess(context *cli.Context, c *libcontainer.Container) (*specs.Process
|
||||
return nil, err
|
||||
}
|
||||
p := spec.Process
|
||||
args := context.Args()
|
||||
args := cmd.Args().Slice()
|
||||
if len(args) < 2 {
|
||||
return nil, errors.New("exec args cannot be empty")
|
||||
}
|
||||
p.Args = args[1:]
|
||||
// Override the cwd, if passed.
|
||||
if cwd := context.String("cwd"); cwd != "" {
|
||||
if cwd := cmd.String("cwd"); cwd != "" {
|
||||
p.Cwd = cwd
|
||||
}
|
||||
if ap := context.String("apparmor"); ap != "" {
|
||||
if ap := cmd.String("apparmor"); ap != "" {
|
||||
p.ApparmorProfile = ap
|
||||
}
|
||||
if l := context.String("process-label"); l != "" {
|
||||
if l := cmd.String("process-label"); l != "" {
|
||||
p.SelinuxLabel = l
|
||||
}
|
||||
if caps := context.StringSlice("cap"); len(caps) > 0 {
|
||||
if caps := cmd.StringSlice("cap"); len(caps) > 0 {
|
||||
for _, c := range caps {
|
||||
p.Capabilities.Bounding = append(p.Capabilities.Bounding, c)
|
||||
p.Capabilities.Effective = append(p.Capabilities.Effective, c)
|
||||
@@ -248,15 +261,15 @@ func getProcess(context *cli.Context, c *libcontainer.Container) (*specs.Process
|
||||
}
|
||||
}
|
||||
// append the passed env variables
|
||||
p.Env = append(p.Env, context.StringSlice("env")...)
|
||||
p.Env = append(p.Env, cmd.StringSlice("env")...)
|
||||
|
||||
// Always set tty to false, unless explicitly enabled from CLI.
|
||||
p.Terminal = context.Bool("tty")
|
||||
if context.IsSet("no-new-privs") {
|
||||
p.NoNewPrivileges = context.Bool("no-new-privs")
|
||||
p.Terminal = cmd.Bool("tty")
|
||||
if cmd.IsSet("no-new-privs") {
|
||||
p.NoNewPrivileges = cmd.Bool("no-new-privs")
|
||||
}
|
||||
// Override the user, if passed.
|
||||
if user := context.String("user"); user != "" {
|
||||
if user := cmd.String("user"); user != "" {
|
||||
uids, gids, ok := strings.Cut(user, ":")
|
||||
if ok {
|
||||
gid, err := strconv.Atoi(gids)
|
||||
@@ -271,7 +284,7 @@ func getProcess(context *cli.Context, c *libcontainer.Container) (*specs.Process
|
||||
}
|
||||
p.User.UID = uint32(uid)
|
||||
}
|
||||
for _, gid := range context.Int64Slice("additional-gids") {
|
||||
for _, gid := range cmd.Int64Slice("additional-gids") {
|
||||
if gid < 0 {
|
||||
return nil, fmt.Errorf("additional-gids must be a positive number %d", gid)
|
||||
}
|
||||
|
||||
+8
-5
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
@@ -11,10 +12,10 @@ import (
|
||||
runcfeatures "github.com/opencontainers/runc/types/features"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/opencontainers/runtime-spec/specs-go/features"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var featuresCommand = cli.Command{
|
||||
var featuresCommand = &cli.Command{
|
||||
Name: "features",
|
||||
Usage: "show the enabled features",
|
||||
ArgsUsage: "",
|
||||
@@ -22,8 +23,10 @@ var featuresCommand = cli.Command{
|
||||
The result is parsable as a JSON.
|
||||
See https://github.com/opencontainers/runtime-spec/blob/main/features.md for the type definition.
|
||||
`,
|
||||
Action: func(context *cli.Context) error {
|
||||
if err := checkArgs(context, 0, exactArgs); err != nil {
|
||||
// Disable comma as separator for slice flags.
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
if err := checkArgs(cmd, 0, exactArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -93,7 +96,7 @@ var featuresCommand = cli.Command{
|
||||
feat.Annotations[runcfeatures.AnnotationLibseccompVersion] = fmt.Sprintf("%d.%d.%d", major, minor, patch)
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(context.App.Writer)
|
||||
enc := json.NewEncoder(cmd.Writer)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(feat)
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ require (
|
||||
github.com/opencontainers/selinux v1.14.1
|
||||
github.com/seccomp/libseccomp-golang v0.11.1
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/urfave/cli v1.22.17
|
||||
github.com/urfave/cli/v3 v3.9.0
|
||||
github.com/vishvananda/netlink v1.3.1
|
||||
github.com/vishvananda/netns v0.0.5
|
||||
golang.org/x/net v0.54.0
|
||||
@@ -31,6 +31,4 @@ require (
|
||||
require (
|
||||
cyphar.com/go-pathrs v0.2.4 // indirect
|
||||
github.com/cilium/ebpf v0.17.3 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
cyphar.com/go-pathrs v0.2.4 h1:iD/mge36swa1UFKdINkr1Frkpp6wZsy3YYEildj9cLY=
|
||||
cyphar.com/go-pathrs v0.2.4/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/checkpoint-restore/go-criu/v7 v7.2.0 h1:qGiWA4App1gGlEfIJ68WR9jbezV9J7yZdjzglezcqKo=
|
||||
github.com/checkpoint-restore/go-criu/v7 v7.2.0/go.mod h1:u0LCWLg0w4yqqu14aXhiB4YD3a1qd8EcCEg7vda5dwo=
|
||||
github.com/cilium/ebpf v0.17.3 h1:FnP4r16PWYSE4ux6zN+//jMcW4nMVRvuTLVTvCjyyjg=
|
||||
@@ -9,11 +8,8 @@ github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/q
|
||||
github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
|
||||
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
@@ -58,24 +54,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/seccomp/libseccomp-golang v0.11.1 h1:wuk4ZjSx6kyQII4rj6G6fvVzRHQaSiPvccJazDagu4g=
|
||||
github.com/seccomp/libseccomp-golang v0.11.1/go.mod h1:5m1Lk8E9OwgZTTVz4bBOer7JuazaBa+xTkM895tDiWc=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
|
||||
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
|
||||
github.com/urfave/cli/v3 v3.9.0 h1:AV9lIiPv3ukYnxunaCUsHnEozptYmDN2F0+yWqLMn/c=
|
||||
github.com/urfave/cli/v3 v3.9.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||
@@ -91,8 +77,5 @@ golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var killCommand = cli.Command{
|
||||
var killCommand = &cli.Command{
|
||||
Name: "kill",
|
||||
Usage: "kill sends the specified signal (default: SIGTERM) to the container's init process",
|
||||
ArgsUsage: `<container-id> [signal]
|
||||
@@ -24,26 +25,31 @@ For example, if the container id is "ubuntu01" the following will send a "KILL"
|
||||
signal to the init process of the "ubuntu01" container:
|
||||
|
||||
# runc kill ubuntu01 KILL`,
|
||||
// Stop parsing flags after the first positional argument (container ID).
|
||||
StopOnNthArg: mkPtr(1),
|
||||
// Disable comma as separator for slice flags.
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "all, a",
|
||||
Usage: "(obsoleted, do not use)",
|
||||
Hidden: true,
|
||||
&cli.BoolFlag{
|
||||
Name: "all",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "(obsoleted, do not use)",
|
||||
Hidden: true,
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
if err := checkArgs(context, 1, minArgs); err != nil {
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
if err := checkArgs(cmd, 1, minArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkArgs(context, 2, maxArgs); err != nil {
|
||||
if err := checkArgs(cmd, 2, maxArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
container, err := getContainer(context)
|
||||
container, err := getContainer(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sigstr := context.Args().Get(1)
|
||||
sigstr := cmd.Args().Get(1)
|
||||
if sigstr == "" {
|
||||
sigstr = "SIGTERM"
|
||||
}
|
||||
@@ -53,7 +59,7 @@ signal to the init process of the "ubuntu01" container:
|
||||
return err
|
||||
}
|
||||
err = container.Signal(signal)
|
||||
if errors.Is(err, libcontainer.ErrNotRunning) && context.Bool("all") {
|
||||
if errors.Is(err, libcontainer.ErrNotRunning) && cmd.Bool("all") {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -13,7 +14,7 @@ import (
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
"github.com/opencontainers/runc/libcontainer/utils"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
const formatOptions = `table or json`
|
||||
@@ -41,7 +42,7 @@ type containerState struct {
|
||||
Owner string `json:"owner"`
|
||||
}
|
||||
|
||||
var listCommand = cli.Command{
|
||||
var listCommand = &cli.Command{
|
||||
Name: "list",
|
||||
Usage: "lists containers started by runc with the given root",
|
||||
ArgsUsage: `
|
||||
@@ -56,34 +57,38 @@ To list containers created via the default "--root":
|
||||
EXAMPLE 2:
|
||||
To list containers created using a non-default value for "--root":
|
||||
# runc --root value list`,
|
||||
// Disable comma as separator for slice flags.
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format, f",
|
||||
Value: "table",
|
||||
Usage: `select one of: ` + formatOptions,
|
||||
&cli.StringFlag{
|
||||
Name: "format",
|
||||
Aliases: []string{"f"},
|
||||
Value: "table",
|
||||
Usage: `select one of: ` + formatOptions,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "quiet, q",
|
||||
Usage: "display only container IDs",
|
||||
&cli.BoolFlag{
|
||||
Name: "quiet",
|
||||
Aliases: []string{"q"},
|
||||
Usage: "display only container IDs",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
if err := checkArgs(context, 0, exactArgs); err != nil {
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
if err := checkArgs(cmd, 0, exactArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
s, err := getContainers(context)
|
||||
s, err := getContainers(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if context.Bool("quiet") {
|
||||
if cmd.Bool("quiet") {
|
||||
for _, item := range s {
|
||||
fmt.Println(item.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch context.String("format") {
|
||||
switch cmd.String("format") {
|
||||
case "table":
|
||||
w := tabwriter.NewWriter(os.Stdout, 12, 1, 3, ' ', 0)
|
||||
fmt.Fprint(w, "ID\tPID\tSTATUS\tBUNDLE\tCREATED\tOWNER\n")
|
||||
@@ -110,11 +115,11 @@ To list containers created using a non-default value for "--root":
|
||||
},
|
||||
}
|
||||
|
||||
func getContainers(context *cli.Context) ([]containerState, error) {
|
||||
root := context.GlobalString("root")
|
||||
func getContainers(cmd *cli.Command) ([]containerState, error) {
|
||||
root := cmd.String("root")
|
||||
list, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) && context.IsSet("root") {
|
||||
if errors.Is(err, os.ErrNotExist) && cmd.IsSet("root") {
|
||||
// Ignore non-existing default root directory
|
||||
// (no containers created yet).
|
||||
return nil, nil
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -20,7 +21,7 @@ import (
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
// version is set from the contents of VERSION file.
|
||||
@@ -37,10 +38,10 @@ var extraVersion = ""
|
||||
// and will be populated by the Makefile.
|
||||
var gitCommit = ""
|
||||
|
||||
func printVersion(c *cli.Context) {
|
||||
w := c.App.Writer
|
||||
func printVersion(c *cli.Command) {
|
||||
w := c.Writer
|
||||
|
||||
fmt.Fprintln(w, "runc version", c.App.Version)
|
||||
fmt.Fprintln(w, "runc version", c.Version)
|
||||
if gitCommit != "" {
|
||||
fmt.Fprintln(w, "commit:", gitCommit)
|
||||
}
|
||||
@@ -81,10 +82,12 @@ value for "bundle" is the current directory.`
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app := &cli.Command{}
|
||||
app.Name = "runc"
|
||||
app.Version = strings.TrimSpace(version) + extraVersion
|
||||
app.Usage = usage
|
||||
// Disable comma as separator for slice flags.
|
||||
app.DisableSliceFlagSeparator = true
|
||||
|
||||
cli.VersionPrinter = printVersion
|
||||
|
||||
@@ -97,36 +100,36 @@ func main() {
|
||||
}
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Usage: "enable debug logging",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "log",
|
||||
Value: "",
|
||||
Usage: "set the log file to write runc logs to (default is '/dev/stderr')",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "log-format",
|
||||
Value: "text",
|
||||
Usage: "set the log format ('text' (default), or 'json')",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "root",
|
||||
Value: root,
|
||||
Usage: "root directory for storage of container state (this should be located in tmpfs)",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "systemd-cgroup",
|
||||
Usage: "enable systemd cgroup support, expects cgroupsPath to be of form \"slice:prefix:name\" for e.g. \"system.slice:runc:434234\"",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "rootless",
|
||||
Value: "auto",
|
||||
Usage: "ignore cgroup permission errors ('true', 'false', or 'auto')",
|
||||
},
|
||||
}
|
||||
app.Commands = []cli.Command{
|
||||
app.Commands = []*cli.Command{
|
||||
checkpointCommand,
|
||||
createCommand,
|
||||
deleteCommand,
|
||||
@@ -145,8 +148,8 @@ func main() {
|
||||
updateCommand,
|
||||
featuresCommand,
|
||||
}
|
||||
app.Before = func(context *cli.Context) error {
|
||||
if !context.IsSet("root") && xdgDirUsed {
|
||||
app.Before = func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
|
||||
if !cmd.IsSet("root") && xdgDirUsed {
|
||||
// According to the XDG specification, we need to set anything in
|
||||
// XDG_RUNTIME_DIR to have a sticky bit if we don't want it to get
|
||||
// auto-pruned.
|
||||
@@ -159,18 +162,22 @@ func main() {
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
if err := reviseRootDir(context); err != nil {
|
||||
return err
|
||||
if err := reviseRootDir(cmd); err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
return configLogrus(context)
|
||||
if err := configLogrus(cmd); err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// If the command returns an error, cli takes upon itself to print
|
||||
// the error on cli.ErrWriter and exit.
|
||||
// Use our own writer here to ensure the log gets sent to the right location.
|
||||
cli.ErrWriter = &FatalWriter{cli.ErrWriter}
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
if err := app.Run(context.Background(), os.Args); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -187,8 +194,8 @@ func (f *FatalWriter) Write(p []byte) (n int, err error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func configLogrus(context *cli.Context) error {
|
||||
if context.GlobalBool("debug") {
|
||||
func configLogrus(cmd *cli.Command) error {
|
||||
if cmd.Bool("debug") {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
logrus.SetReportCaller(true)
|
||||
// Shorten function and file names reported by the logger, by
|
||||
@@ -205,7 +212,7 @@ func configLogrus(context *cli.Context) error {
|
||||
})
|
||||
}
|
||||
|
||||
switch f := context.GlobalString("log-format"); f {
|
||||
switch f := cmd.String("log-format"); f {
|
||||
case "":
|
||||
// do nothing
|
||||
case "text":
|
||||
@@ -216,7 +223,7 @@ func configLogrus(context *cli.Context) error {
|
||||
return errors.New("invalid log-format: " + f)
|
||||
}
|
||||
|
||||
if file := context.GlobalString("log"); file != "" {
|
||||
if file := cmd.String("log"); file != "" {
|
||||
f, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+5
-5
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
@@ -25,12 +25,12 @@ type notifySocket struct {
|
||||
socketPath string
|
||||
}
|
||||
|
||||
func newNotifySocket(context *cli.Context, notifySocketHost, id string) *notifySocket {
|
||||
func newNotifySocket(cmd *cli.Command, notifySocketHost, id string) *notifySocket {
|
||||
if notifySocketHost == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
root := filepath.Join(context.GlobalString("root"), id)
|
||||
root := filepath.Join(cmd.String("root"), id)
|
||||
socketPath := filepath.Join(root, "notify", "notify.sock")
|
||||
|
||||
notifySocket := ¬ifySocket{
|
||||
@@ -84,8 +84,8 @@ func (s *notifySocket) setupSocketDirectory() error {
|
||||
return os.Mkdir(path.Dir(s.socketPath), 0o755)
|
||||
}
|
||||
|
||||
func notifySocketStart(context *cli.Context, notifySocketHost, id string) (*notifySocket, error) {
|
||||
notifySocket := newNotifySocket(context, notifySocketHost, id)
|
||||
func notifySocketStart(cmd *cli.Command, notifySocketHost, id string) (*notifySocket, error) {
|
||||
notifySocket := newNotifySocket(cmd, notifySocketHost, id)
|
||||
if notifySocket == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli"
|
||||
"context"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var pauseCommand = cli.Command{
|
||||
var pauseCommand = &cli.Command{
|
||||
Name: "pause",
|
||||
Usage: "pause suspends all processes inside the container",
|
||||
ArgsUsage: `<container-id>
|
||||
@@ -14,11 +16,13 @@ paused. `,
|
||||
Description: `The pause command suspends all processes in the instance of the container.
|
||||
|
||||
Use runc list to identify instances of containers and their current status.`,
|
||||
Action: func(context *cli.Context) error {
|
||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
||||
// Disable comma as separator for slice flags.
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
container, err := getContainer(context)
|
||||
container, err := getContainer(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -31,7 +35,7 @@ Use runc list to identify instances of containers and their current status.`,
|
||||
},
|
||||
}
|
||||
|
||||
var resumeCommand = cli.Command{
|
||||
var resumeCommand = &cli.Command{
|
||||
Name: "resume",
|
||||
Usage: "resumes all processes that have been previously paused",
|
||||
ArgsUsage: `<container-id>
|
||||
@@ -41,11 +45,13 @@ resumed.`,
|
||||
Description: `The resume command resumes all processes in the instance of the container.
|
||||
|
||||
Use runc list to identify instances of containers and their current status.`,
|
||||
Action: func(context *cli.Context) error {
|
||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
||||
// Disable comma as separator for slice flags.
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
container, err := getContainer(context)
|
||||
container, err := getContainer(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -10,26 +11,32 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var psCommand = cli.Command{
|
||||
var psCommand = &cli.Command{
|
||||
Name: "ps",
|
||||
Usage: "ps displays the processes running inside a container",
|
||||
ArgsUsage: `<container-id> [ps options]`,
|
||||
// Stop parsing flags after the first positional argument (the container ID).
|
||||
// This allows passing flags like -aux to the underlying ps command.
|
||||
StopOnNthArg: mkPtr(1),
|
||||
// Disable comma as separator for slice flags.
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format, f",
|
||||
Value: "table",
|
||||
Usage: `select one of: ` + formatOptions,
|
||||
&cli.StringFlag{
|
||||
Name: "format",
|
||||
Aliases: []string{"f"},
|
||||
Value: "table",
|
||||
Usage: `select one of: ` + formatOptions,
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
if err := checkArgs(context, 1, minArgs); err != nil {
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
if err := checkArgs(cmd, 1, minArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
container, err := getContainer(context)
|
||||
container, err := getContainer(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -40,7 +47,7 @@ var psCommand = cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
switch context.String("format") {
|
||||
switch cmd.String("format") {
|
||||
case "table":
|
||||
case "json":
|
||||
return json.NewEncoder(os.Stdout).Encode(pids)
|
||||
@@ -49,16 +56,16 @@ var psCommand = cli.Command{
|
||||
}
|
||||
|
||||
// [1:] is to remove command name, ex:
|
||||
// context.Args(): [container_id ps_arg1 ps_arg2 ...]
|
||||
// cmd.Args(): [container_id ps_arg1 ps_arg2 ...]
|
||||
// psArgs: [ps_arg1 ps_arg2 ...]
|
||||
//
|
||||
psArgs := context.Args()[1:]
|
||||
psArgs := cmd.Args().Slice()[1:]
|
||||
if len(psArgs) == 0 {
|
||||
psArgs = []string{"-ef"}
|
||||
}
|
||||
|
||||
cmd := exec.Command("ps", psArgs...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
cmdExec := exec.Command("ps", psArgs...)
|
||||
output, err := cmdExec.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", err, output)
|
||||
}
|
||||
@@ -86,7 +93,6 @@ var psCommand = cli.Command{
|
||||
}
|
||||
return nil
|
||||
},
|
||||
SkipArgReorder: true,
|
||||
}
|
||||
|
||||
func getPidIndex(title string) (int, error) {
|
||||
|
||||
+34
-29
@@ -1,14 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/moby/sys/userns"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var restoreCommand = cli.Command{
|
||||
var restoreCommand = &cli.Command{
|
||||
Name: "restore",
|
||||
Usage: "restore a container from a previous checkpoint",
|
||||
ArgsUsage: `<container-id>
|
||||
@@ -17,90 +18,94 @@ Where "<container-id>" is the name for the instance of the container to be
|
||||
restored.`,
|
||||
Description: `Restores the saved state of the container instance that was previously saved
|
||||
using the runc checkpoint command.`,
|
||||
// Disable comma as separator for slice flags.
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "console-socket",
|
||||
Value: "",
|
||||
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "image-path",
|
||||
Value: "",
|
||||
Usage: "path to criu image files for restoring",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "work-path",
|
||||
Value: "",
|
||||
Usage: "path for saving work files and logs",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "tcp-established",
|
||||
Usage: "allow open tcp connections",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "ext-unix-sk",
|
||||
Usage: "allow external unix sockets",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "shell-job",
|
||||
Usage: "allow shell jobs",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "file-locks",
|
||||
Usage: "handle file locks, for safety",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "manage-cgroups-mode",
|
||||
Value: "",
|
||||
Usage: "cgroups mode: soft|full|strict|ignore (default: soft)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "bundle, b",
|
||||
Value: "",
|
||||
Usage: "path to the root of the bundle directory",
|
||||
&cli.StringFlag{
|
||||
Name: "bundle",
|
||||
Aliases: []string{"b"},
|
||||
Value: "",
|
||||
Usage: "path to the root of the bundle directory",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "detach,d",
|
||||
Usage: "detach from the container's process",
|
||||
&cli.BoolFlag{
|
||||
Name: "detach",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "detach from the container's process",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "pid-file",
|
||||
Value: "",
|
||||
Usage: "specify the file to write the process id to",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "no-subreaper",
|
||||
Usage: "disable the use of the subreaper used to reap reparented processes",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "no-pivot",
|
||||
Usage: "do not use pivot root to jail process inside rootfs. This should be used whenever the rootfs is on top of a ramdisk",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
&cli.StringSliceFlag{
|
||||
Name: "empty-ns",
|
||||
Usage: "create a namespace, but don't restore its properties",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "auto-dedup",
|
||||
Usage: "enable auto deduplication of memory images",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "lazy-pages",
|
||||
Usage: "use userfaultfd to lazily restore memory pages",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "lsm-profile",
|
||||
Value: "",
|
||||
Usage: "Specify an LSM profile to be used during restore in the form of TYPE:NAME.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "lsm-mount-context",
|
||||
Value: "",
|
||||
Usage: "Specify an LSM mount context to be used during restore.",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
// XXX: Currently this is untested with rootless containers.
|
||||
@@ -108,11 +113,11 @@ using the runc checkpoint command.`,
|
||||
logrus.Warn("runc checkpoint is untested with rootless containers")
|
||||
}
|
||||
|
||||
options, err := criuOptions(context)
|
||||
options, err := criuOptions(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status, err := startContainer(context, CT_ACT_RESTORE, options)
|
||||
status, err := startContainer(cmd, CT_ACT_RESTORE, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+5
-5
@@ -5,14 +5,14 @@ import (
|
||||
|
||||
"github.com/moby/sys/userns"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"github.com/opencontainers/cgroups/systemd"
|
||||
)
|
||||
|
||||
func shouldUseRootlessCgroupManager(context *cli.Context) (bool, error) {
|
||||
if context != nil {
|
||||
b, err := parseBoolOrAuto(context.GlobalString("rootless"))
|
||||
func shouldUseRootlessCgroupManager(cmd *cli.Command) (bool, error) {
|
||||
if cmd != nil {
|
||||
b, err := parseBoolOrAuto(cmd.String("rootless"))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -37,7 +37,7 @@ func shouldUseRootlessCgroupManager(context *cli.Context) (bool, error) {
|
||||
//
|
||||
// On error, we assume we are root. An error may happen during shelling out to `busctl` CLI,
|
||||
// mostly when $DBUS_SESSION_BUS_ADDRESS is unset.
|
||||
if context.GlobalBool("systemd-cgroup") {
|
||||
if cmd.Bool("systemd-cgroup") {
|
||||
ownerUID, err := systemd.DetectUID()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Debug("failed to get the OwnerUID value, assuming the value to be 0")
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
// default action is to start a container
|
||||
var runCommand = cli.Command{
|
||||
var runCommand = &cli.Command{
|
||||
Name: "run",
|
||||
Usage: "create and run a container",
|
||||
ArgsUsage: `<container-id>
|
||||
@@ -24,56 +25,60 @@ The specification file includes an args parameter. The args parameter is used
|
||||
to specify command(s) that get run when the container is started. To change the
|
||||
command(s) that get executed on start, edit the args parameter of the spec. See
|
||||
"runc spec --help" for more explanation.`,
|
||||
// Disable comma as separator for slice flags.
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "bundle, b",
|
||||
Value: "",
|
||||
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
||||
&cli.StringFlag{
|
||||
Name: "bundle",
|
||||
Aliases: []string{"b"},
|
||||
Value: "",
|
||||
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "console-socket",
|
||||
Value: "",
|
||||
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "pidfd-socket",
|
||||
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the init process",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "detach, d",
|
||||
Usage: "detach from the container's process",
|
||||
&cli.BoolFlag{
|
||||
Name: "detach",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "detach from the container's process",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "keep",
|
||||
Usage: "do not delete the container after it exits",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "pid-file",
|
||||
Value: "",
|
||||
Usage: "specify the file to write the process id to",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "no-subreaper",
|
||||
Usage: "disable the use of the subreaper used to reap reparented processes",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "no-pivot",
|
||||
Usage: "do not use pivot root to jail process inside rootfs. This should be used whenever the rootfs is on top of a ramdisk",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "no-new-keyring",
|
||||
Usage: "do not create a new session keyring for the container. This will cause the container to inherit the calling processes session key",
|
||||
},
|
||||
cli.IntFlag{
|
||||
&cli.IntFlag{
|
||||
Name: "preserve-fds",
|
||||
Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
status, err := startContainer(context, CT_ACT_RUN, nil)
|
||||
status, err := startContainer(cmd, CT_ACT_RUN, nil)
|
||||
if err == nil {
|
||||
// exit with the container's exit status so any external supervisor is
|
||||
// notified of the exit with the correct exit status.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -9,10 +10,10 @@ import (
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/opencontainers/runc/libcontainer/specconv"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var specCommand = cli.Command{
|
||||
var specCommand = &cli.Command{
|
||||
Name: "spec",
|
||||
Usage: "create a new specification file",
|
||||
ArgsUsage: "",
|
||||
@@ -64,24 +65,27 @@ generate a proper rootless spec file.
|
||||
Note that --rootless is not needed when you execute runc as the root in a user namespace
|
||||
created by an unprivileged user.
|
||||
`,
|
||||
// Disable comma as separator for slice flags.
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "bundle, b",
|
||||
Value: "",
|
||||
Usage: "path to the root of the bundle directory",
|
||||
&cli.StringFlag{
|
||||
Name: "bundle",
|
||||
Aliases: []string{"b"},
|
||||
Value: "",
|
||||
Usage: "path to the root of the bundle directory",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "rootless",
|
||||
Usage: "generate a configuration for a rootless container",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
if err := checkArgs(context, 0, exactArgs); err != nil {
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
if err := checkArgs(cmd, 0, exactArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
spec := specconv.Example()
|
||||
|
||||
rootless := context.Bool("rootless")
|
||||
rootless := cmd.Bool("rootless")
|
||||
if rootless {
|
||||
specconv.ToRootless(spec)
|
||||
}
|
||||
@@ -96,7 +100,7 @@ created by an unprivileged user.
|
||||
}
|
||||
return nil
|
||||
}
|
||||
bundle := context.String("bundle")
|
||||
bundle := cmd.String("bundle")
|
||||
if bundle != "" {
|
||||
if err := os.Chdir(bundle); err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var startCommand = cli.Command{
|
||||
var startCommand = &cli.Command{
|
||||
Name: "start",
|
||||
Usage: "executes the user defined process in a created container",
|
||||
ArgsUsage: `<container-id>
|
||||
@@ -18,11 +19,13 @@ Where "<container-id>" is your name for the instance of the container that you
|
||||
are starting. The name you provide for the container instance must be unique on
|
||||
your host.`,
|
||||
Description: `The start command executes the user defined process in a created container.`,
|
||||
Action: func(context *cli.Context) error {
|
||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
||||
// Disable comma as separator for slice flags.
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
container, err := getContainer(context)
|
||||
container, err := getContainer(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -32,7 +35,7 @@ your host.`,
|
||||
}
|
||||
switch status {
|
||||
case libcontainer.Created:
|
||||
notifySocket, err := notifySocketStart(context, os.Getenv("NOTIFY_SOCKET"), container.ID())
|
||||
notifySocket, err := notifySocketStart(cmd, os.Getenv("NOTIFY_SOCKET"), container.ID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
"github.com/opencontainers/runc/libcontainer/utils"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var stateCommand = cli.Command{
|
||||
var stateCommand = &cli.Command{
|
||||
Name: "state",
|
||||
Usage: "output the state of a container",
|
||||
ArgsUsage: `<container-id>
|
||||
@@ -17,11 +18,13 @@ var stateCommand = cli.Command{
|
||||
Where "<container-id>" is your name for the instance of the container.`,
|
||||
Description: `The state command outputs current state information for the
|
||||
instance of a container.`,
|
||||
Action: func(context *cli.Context) error {
|
||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
||||
// Disable comma as separator for slice flags.
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
container, err := getContainer(context)
|
||||
container, err := getContainer(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/opencontainers/runc/internal/cmsg"
|
||||
@@ -32,32 +33,32 @@ pidfd:
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app := &cli.Command{}
|
||||
app.Name = "pidfd-kill"
|
||||
app.Usage = usage
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "signal",
|
||||
Value: "SIGKILL",
|
||||
Usage: "Signal to send to the init process",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "pid-file",
|
||||
Value: "",
|
||||
Usage: "Path to write the pidfd-kill process ID to",
|
||||
},
|
||||
}
|
||||
|
||||
app.Action = func(ctx *cli.Context) error {
|
||||
args := ctx.Args()
|
||||
app.Action = func(_ context.Context, cmd *cli.Command) error {
|
||||
args := cmd.Args().Slice()
|
||||
if len(args) != 1 {
|
||||
return errors.New("required a single socket path")
|
||||
}
|
||||
|
||||
socketFile := ctx.Args()[0]
|
||||
socketFile := args[0]
|
||||
|
||||
pidFile := ctx.String("pid-file")
|
||||
pidFile := cmd.String("pid-file")
|
||||
if pidFile != "" {
|
||||
pid := fmt.Sprintf("%d\n", os.Getpid())
|
||||
if err := os.WriteFile(pidFile, []byte(pid), 0o644); err != nil {
|
||||
@@ -66,7 +67,7 @@ func main() {
|
||||
defer os.Remove(pidFile)
|
||||
}
|
||||
|
||||
sigStr := ctx.String("signal")
|
||||
sigStr := cmd.String("signal")
|
||||
if sigStr == "" {
|
||||
sigStr = "SIGKILL"
|
||||
}
|
||||
@@ -84,7 +85,7 @@ func main() {
|
||||
|
||||
return unix.PidfdSendSignal(int(pidfdFile.Fd()), sig, nil, 0)
|
||||
}
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
if err := app.Run(context.Background(), os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "fatal error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -34,7 +35,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/console"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"github.com/opencontainers/runc/internal/cmsg"
|
||||
)
|
||||
@@ -175,7 +176,7 @@ func handleNull(path string) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app := &cli.Command{}
|
||||
app.Name = "recvtty"
|
||||
app.Usage = usage
|
||||
|
||||
@@ -191,30 +192,31 @@ func main() {
|
||||
|
||||
// Set the flags.
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "mode, m",
|
||||
Value: "single",
|
||||
Usage: "Mode of operation (single or null)",
|
||||
&cli.StringFlag{
|
||||
Name: "mode",
|
||||
Aliases: []string{"m"},
|
||||
Value: "single",
|
||||
Usage: "Mode of operation (single or null)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "pid-file",
|
||||
Value: "",
|
||||
Usage: "Path to write daemon process ID to",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "no-stdin",
|
||||
Usage: "Disable stdin handling (no-op for null mode)",
|
||||
},
|
||||
}
|
||||
|
||||
app.Action = func(ctx *cli.Context) error {
|
||||
args := ctx.Args()
|
||||
app.Action = func(_ context.Context, cmd *cli.Command) error {
|
||||
args := cmd.Args().Slice()
|
||||
if len(args) != 1 {
|
||||
return errors.New("need to specify a single socket path")
|
||||
}
|
||||
path := ctx.Args()[0]
|
||||
path := args[0]
|
||||
|
||||
pidPath := ctx.String("pid-file")
|
||||
pidPath := cmd.String("pid-file")
|
||||
if pidPath != "" {
|
||||
pid := fmt.Sprintf("%d\n", os.Getpid())
|
||||
if err := os.WriteFile(pidPath, []byte(pid), 0o644); err != nil {
|
||||
@@ -222,8 +224,8 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
noStdin := ctx.Bool("no-stdin")
|
||||
switch ctx.String("mode") {
|
||||
noStdin := cmd.Bool("no-stdin")
|
||||
switch cmd.String("mode") {
|
||||
case "single":
|
||||
if err := handleSingle(path, noStdin); err != nil {
|
||||
return err
|
||||
@@ -233,11 +235,11 @@ func main() {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("need to select a valid mode: %s", ctx.String("mode"))
|
||||
return fmt.Errorf("need to select a valid mode: %s", cmd.String("mode"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
if err := app.Run(context.Background(), os.Args); err != nil {
|
||||
bail(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -13,7 +14,7 @@ import (
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
@@ -102,12 +103,12 @@ func remapRootfs(root string, uidMap, gidMap []specs.LinuxIDMapping) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app := &cli.Command{}
|
||||
app.Name = "remap-rootfs"
|
||||
app.Usage = usage
|
||||
|
||||
app.Action = func(ctx *cli.Context) error {
|
||||
args := ctx.Args()
|
||||
app.Action = func(_ context.Context, cmd *cli.Command) error {
|
||||
args := cmd.Args().Slice()
|
||||
if len(args) != 1 {
|
||||
return errors.New("exactly one bundle argument must be provided")
|
||||
}
|
||||
@@ -141,7 +142,7 @@ func main() {
|
||||
|
||||
return remapRootfs(rootfs, uidMap, gidMap)
|
||||
}
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
if err := app.Run(context.Background(), os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -14,19 +15,22 @@ import (
|
||||
"github.com/docker/go-units"
|
||||
"github.com/opencontainers/runc/libcontainer/intelrdt"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func mkPtr[T any](v T) *T { return &v }
|
||||
|
||||
var updateCommand = cli.Command{
|
||||
var updateCommand = &cli.Command{
|
||||
Name: "update",
|
||||
Usage: "update container resource constraints",
|
||||
ArgsUsage: `<container-id>`,
|
||||
// Disable comma as separator for slice flags.
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "resources, r",
|
||||
Value: "",
|
||||
&cli.StringFlag{
|
||||
Name: "resources",
|
||||
Aliases: []string{"r"},
|
||||
Value: "",
|
||||
Usage: `path to the file containing the resources to update or '-' to read from the standard input
|
||||
|
||||
The accepted format is as follow (unchanged values can be omitted):
|
||||
@@ -59,86 +63,86 @@ other options are ignored.
|
||||
`,
|
||||
},
|
||||
|
||||
cli.IntFlag{
|
||||
&cli.IntFlag{
|
||||
Name: "blkio-weight",
|
||||
Usage: "Specifies per cgroup weight, range is from 10 to 1000",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "cpu-period",
|
||||
Usage: "CPU CFS period to be used for hardcapping (in usecs). 0 to use system default",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "cpu-quota",
|
||||
Usage: "CPU CFS hardcap limit (in usecs). Allowed cpu time in a given period",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "cpu-burst",
|
||||
Usage: "CPU CFS hardcap burst limit (in usecs). Allowed accumulated cpu time additionally for burst a given period",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "cpu-share",
|
||||
Usage: "CPU shares (relative weight vs. other containers)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "cpu-rt-period",
|
||||
Usage: "CPU realtime period to be used for hardcapping (in usecs). 0 to use system default",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "cpu-rt-runtime",
|
||||
Usage: "CPU realtime hardcap limit (in usecs). Allowed cpu time in a given period",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "cpuset-cpus",
|
||||
Usage: "CPU(s) to use",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "cpuset-mems",
|
||||
Usage: "Memory node(s) to use",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "kernel-memory",
|
||||
Usage: "(obsoleted; do not use)",
|
||||
Hidden: true,
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "kernel-memory-tcp",
|
||||
Usage: "(obsoleted; do not use)",
|
||||
Hidden: true,
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "memory",
|
||||
Usage: "Memory limit (in bytes)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "cpu-idle",
|
||||
Usage: "set cgroup SCHED_IDLE or not, 0: default behavior, 1: SCHED_IDLE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "memory-reservation",
|
||||
Usage: "Memory reservation or soft_limit (in bytes)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "memory-swap",
|
||||
Usage: "Total memory usage (memory + swap); set '-1' to enable unlimited swap",
|
||||
},
|
||||
cli.IntFlag{
|
||||
&cli.IntFlag{
|
||||
Name: "pids-limit",
|
||||
Usage: "Maximum number of pids allowed in the container",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "l3-cache-schema",
|
||||
Usage: "The string of Intel RDT/CAT L3 cache schema",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "mem-bw-schema",
|
||||
Usage: "The string of Intel RDT/MBA memory bandwidth schema",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
container, err := getContainer(context)
|
||||
container, err := getContainer(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -155,7 +159,7 @@ other options are ignored.
|
||||
|
||||
config := container.Config()
|
||||
|
||||
if in := context.String("resources"); in != "" {
|
||||
if in := cmd.String("resources"); in != "" {
|
||||
var (
|
||||
f *os.File
|
||||
err error
|
||||
@@ -175,16 +179,16 @@ other options are ignored.
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if val := context.Int("blkio-weight"); val != 0 {
|
||||
if val := cmd.Int("blkio-weight"); val != 0 {
|
||||
r.BlockIO.Weight = mkPtr(uint16(val))
|
||||
}
|
||||
if val := context.String("cpuset-cpus"); val != "" {
|
||||
if val := cmd.String("cpuset-cpus"); val != "" {
|
||||
r.CPU.Cpus = val
|
||||
}
|
||||
if val := context.String("cpuset-mems"); val != "" {
|
||||
if val := cmd.String("cpuset-mems"); val != "" {
|
||||
r.CPU.Mems = val
|
||||
}
|
||||
if val := context.String("cpu-idle"); val != "" {
|
||||
if val := cmd.String("cpu-idle"); val != "" {
|
||||
idle, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value for cpu-idle: %w", err)
|
||||
@@ -201,7 +205,7 @@ other options are ignored.
|
||||
{"cpu-rt-period", &r.CPU.RealtimePeriod},
|
||||
{"cpu-share", &r.CPU.Shares},
|
||||
} {
|
||||
if val := context.String(pair.opt); val != "" {
|
||||
if val := cmd.String(pair.opt); val != "" {
|
||||
v, err := strconv.ParseUint(val, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value for %s: %w", pair.opt, err)
|
||||
@@ -216,7 +220,7 @@ other options are ignored.
|
||||
{"cpu-quota", &r.CPU.Quota},
|
||||
{"cpu-rt-runtime", &r.CPU.RealtimeRuntime},
|
||||
} {
|
||||
if val := context.String(pair.opt); val != "" {
|
||||
if val := cmd.String(pair.opt); val != "" {
|
||||
v, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value for %s: %w", pair.opt, err)
|
||||
@@ -234,7 +238,7 @@ other options are ignored.
|
||||
{"kernel-memory-tcp", &r.Memory.KernelTCP},
|
||||
{"memory-reservation", &r.Memory.Reservation},
|
||||
} {
|
||||
if val := context.String(pair.opt); val != "" {
|
||||
if val := cmd.String(pair.opt); val != "" {
|
||||
var v int64
|
||||
|
||||
if val != "-1" {
|
||||
@@ -249,8 +253,8 @@ other options are ignored.
|
||||
}
|
||||
}
|
||||
|
||||
if context.IsSet("pids-limit") {
|
||||
r.Pids.Limit = mkPtr(int64(context.Int("pids-limit")))
|
||||
if cmd.IsSet("pids-limit") {
|
||||
r.Pids.Limit = mkPtr(int64(cmd.Int("pids-limit")))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,8 +363,8 @@ other options are ignored.
|
||||
config.Cgroups.Resources.Unified = r.Unified
|
||||
|
||||
// Update Intel RDT
|
||||
l3CacheSchema := context.String("l3-cache-schema")
|
||||
memBwSchema := context.String("mem-bw-schema")
|
||||
l3CacheSchema := cmd.String("l3-cache-schema")
|
||||
memBwSchema := cmd.String("mem-bw-schema")
|
||||
if l3CacheSchema != "" && !intelrdt.IsCATEnabled() {
|
||||
return errors.New("Intel RDT/CAT: l3 cache schema is not enabled")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -20,27 +21,27 @@ const (
|
||||
maxArgs
|
||||
)
|
||||
|
||||
func checkArgs(context *cli.Context, expected, checkType int) error {
|
||||
func checkArgs(cmd *cli.Command, expected, checkType int) error {
|
||||
var err error
|
||||
cmdName := context.Command.Name
|
||||
cmdName := cmd.Name
|
||||
switch checkType {
|
||||
case exactArgs:
|
||||
if context.NArg() != expected {
|
||||
if cmd.NArg() != expected {
|
||||
err = fmt.Errorf("%s: %q requires exactly %d argument(s)", os.Args[0], cmdName, expected)
|
||||
}
|
||||
case minArgs:
|
||||
if context.NArg() < expected {
|
||||
if cmd.NArg() < expected {
|
||||
err = fmt.Errorf("%s: %q requires a minimum of %d argument(s)", os.Args[0], cmdName, expected)
|
||||
}
|
||||
case maxArgs:
|
||||
if context.NArg() > expected {
|
||||
if cmd.NArg() > expected {
|
||||
err = fmt.Errorf("%s: %q requires a maximum of %d argument(s)", os.Args[0], cmdName, expected)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Incorrect Usage.\n\n")
|
||||
_ = cli.ShowCommandHelp(context, cmdName)
|
||||
_ = cli.ShowCommandHelp(context.Background(), cmd, cmdName)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -68,8 +69,8 @@ func fatalWithCode(err error, ret int) {
|
||||
}
|
||||
|
||||
// setupSpec performs initial setup based on the cli.Context for the container
|
||||
func setupSpec(context *cli.Context) (*specs.Spec, error) {
|
||||
bundle := context.String("bundle")
|
||||
func setupSpec(cmd *cli.Command) (*specs.Spec, error) {
|
||||
bundle := cmd.String("bundle")
|
||||
if bundle != "" {
|
||||
if err := os.Chdir(bundle); err != nil {
|
||||
return nil, err
|
||||
@@ -82,8 +83,8 @@ func setupSpec(context *cli.Context) (*specs.Spec, error) {
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
func revisePidFile(context *cli.Context) error {
|
||||
pidFile := context.String("pid-file")
|
||||
func revisePidFile(cmd *cli.Command) error {
|
||||
pidFile := cmd.String("pid-file")
|
||||
if pidFile == "" {
|
||||
return nil
|
||||
}
|
||||
@@ -94,17 +95,17 @@ func revisePidFile(context *cli.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return context.Set("pid-file", pidFile)
|
||||
return cmd.Set("pid-file", pidFile)
|
||||
}
|
||||
|
||||
// reviseRootDir ensures that the --root option argument,
|
||||
// if specified, is converted to an absolute and cleaned path,
|
||||
// and that this path is sane.
|
||||
func reviseRootDir(context *cli.Context) error {
|
||||
if !context.IsSet("root") {
|
||||
func reviseRootDir(cmd *cli.Command) error {
|
||||
if !cmd.IsSet("root") {
|
||||
return nil
|
||||
}
|
||||
root, err := filepath.Abs(context.GlobalString("root"))
|
||||
root, err := filepath.Abs(cmd.String("root"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -117,7 +118,7 @@ func reviseRootDir(context *cli.Context) error {
|
||||
return errors.New("Option --root argument should not be set to /")
|
||||
}
|
||||
|
||||
return context.GlobalSet("root", root)
|
||||
return cmd.Set("root", root)
|
||||
}
|
||||
|
||||
// parseBoolOrAuto returns (nil, nil) if s is empty or "auto"
|
||||
|
||||
+23
-23
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
selinux "github.com/opencontainers/selinux/go-selinux"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v3"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/opencontainers/runc/internal/pathrs"
|
||||
@@ -29,12 +29,12 @@ var errEmptyID = errors.New("container id cannot be empty")
|
||||
|
||||
// getContainer returns the specified container instance by loading it from
|
||||
// a state directory (root).
|
||||
func getContainer(context *cli.Context) (*libcontainer.Container, error) {
|
||||
id := context.Args().First()
|
||||
func getContainer(cmd *cli.Command) (*libcontainer.Container, error) {
|
||||
id := cmd.Args().First()
|
||||
if id == "" {
|
||||
return nil, errEmptyID
|
||||
}
|
||||
root := context.GlobalString("root")
|
||||
root := cmd.String("root")
|
||||
return libcontainer.Load(root, id)
|
||||
}
|
||||
|
||||
@@ -181,16 +181,16 @@ func createPidFile(path string, process *libcontainer.Process) error {
|
||||
return os.Rename(tmpName, path)
|
||||
}
|
||||
|
||||
func createContainer(context *cli.Context, id string, spec *specs.Spec) (*libcontainer.Container, error) {
|
||||
rootlessCg, err := shouldUseRootlessCgroupManager(context)
|
||||
func createContainer(cmd *cli.Command, id string, spec *specs.Spec) (*libcontainer.Container, error) {
|
||||
rootlessCg, err := shouldUseRootlessCgroupManager(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config, err := specconv.CreateLibcontainerConfig(&specconv.CreateOpts{
|
||||
CgroupName: id,
|
||||
UseSystemdCgroup: context.GlobalBool("systemd-cgroup"),
|
||||
NoPivotRoot: context.Bool("no-pivot"),
|
||||
NoNewKeyring: context.Bool("no-new-keyring"),
|
||||
UseSystemdCgroup: cmd.Bool("systemd-cgroup"),
|
||||
NoPivotRoot: cmd.Bool("no-pivot"),
|
||||
NoNewKeyring: cmd.Bool("no-new-keyring"),
|
||||
Spec: spec,
|
||||
RootlessEUID: os.Geteuid() != 0,
|
||||
RootlessCgroups: rootlessCg,
|
||||
@@ -199,7 +199,7 @@ func createContainer(context *cli.Context, id string, spec *specs.Spec) (*libcon
|
||||
return nil, err
|
||||
}
|
||||
|
||||
root := context.GlobalString("root")
|
||||
root := cmd.String("root")
|
||||
return libcontainer.Create(root, id, config)
|
||||
}
|
||||
|
||||
@@ -378,26 +378,26 @@ const (
|
||||
CT_ACT_RESTORE
|
||||
)
|
||||
|
||||
func startContainer(context *cli.Context, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) {
|
||||
if err := revisePidFile(context); err != nil {
|
||||
func startContainer(cmd *cli.Command, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) {
|
||||
if err := revisePidFile(cmd); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
spec, err := setupSpec(context)
|
||||
spec, err := setupSpec(cmd)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
id := context.Args().First()
|
||||
id := cmd.Args().First()
|
||||
if id == "" {
|
||||
return -1, errEmptyID
|
||||
}
|
||||
|
||||
notifySocket := newNotifySocket(context, os.Getenv("NOTIFY_SOCKET"), id)
|
||||
notifySocket := newNotifySocket(cmd, os.Getenv("NOTIFY_SOCKET"), id)
|
||||
if notifySocket != nil {
|
||||
notifySocket.setupSpec(spec)
|
||||
}
|
||||
|
||||
container, err := createContainer(context, id, spec)
|
||||
container, err := createContainer(cmd, id, spec)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
@@ -414,16 +414,16 @@ func startContainer(context *cli.Context, action CtAct, criuOpts *libcontainer.C
|
||||
}
|
||||
|
||||
r := &runner{
|
||||
enableSubreaper: !context.Bool("no-subreaper"),
|
||||
shouldDestroy: !context.Bool("keep"),
|
||||
enableSubreaper: !cmd.Bool("no-subreaper"),
|
||||
shouldDestroy: !cmd.Bool("keep"),
|
||||
container: container,
|
||||
listenFDs: activation.Files(), // On-demand socket activation.
|
||||
notifySocket: notifySocket,
|
||||
consoleSocket: context.String("console-socket"),
|
||||
pidfdSocket: context.String("pidfd-socket"),
|
||||
detach: context.Bool("detach"),
|
||||
pidFile: context.String("pid-file"),
|
||||
preserveFDs: context.Int("preserve-fds"),
|
||||
consoleSocket: cmd.String("console-socket"),
|
||||
pidfdSocket: cmd.String("pidfd-socket"),
|
||||
detach: cmd.Bool("detach"),
|
||||
pidFile: cmd.String("pid-file"),
|
||||
preserveFDs: cmd.Int("preserve-fds"),
|
||||
action: action,
|
||||
criuOpts: criuOpts,
|
||||
init: true,
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Brian Goff
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package md2man
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/russross/blackfriday/v2"
|
||||
)
|
||||
|
||||
func fmtListFlags(flags blackfriday.ListType) string {
|
||||
knownFlags := []struct {
|
||||
name string
|
||||
flag blackfriday.ListType
|
||||
}{
|
||||
{"ListTypeOrdered", blackfriday.ListTypeOrdered},
|
||||
{"ListTypeDefinition", blackfriday.ListTypeDefinition},
|
||||
{"ListTypeTerm", blackfriday.ListTypeTerm},
|
||||
{"ListItemContainsBlock", blackfriday.ListItemContainsBlock},
|
||||
{"ListItemBeginningOfList", blackfriday.ListItemBeginningOfList},
|
||||
{"ListItemEndOfList", blackfriday.ListItemEndOfList},
|
||||
}
|
||||
|
||||
var f []string
|
||||
for _, kf := range knownFlags {
|
||||
if flags&kf.flag != 0 {
|
||||
f = append(f, kf.name)
|
||||
flags &^= kf.flag
|
||||
}
|
||||
}
|
||||
if flags != 0 {
|
||||
f = append(f, fmt.Sprintf("Unknown(%#x)", flags))
|
||||
}
|
||||
return strings.Join(f, "|")
|
||||
}
|
||||
|
||||
type debugDecorator struct {
|
||||
blackfriday.Renderer
|
||||
}
|
||||
|
||||
func depth(node *blackfriday.Node) int {
|
||||
d := 0
|
||||
for n := node.Parent; n != nil; n = n.Parent {
|
||||
d++
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *debugDecorator) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
|
||||
fmt.Fprintf(os.Stderr, "%s%s %v %v\n",
|
||||
strings.Repeat(" ", depth(node)),
|
||||
map[bool]string{true: "+", false: "-"}[entering],
|
||||
node,
|
||||
fmtListFlags(node.ListFlags))
|
||||
var b strings.Builder
|
||||
status := d.Renderer.RenderNode(io.MultiWriter(&b, w), node, entering)
|
||||
if b.Len() > 0 {
|
||||
fmt.Fprintf(os.Stderr, ">> %q\n", b.String())
|
||||
}
|
||||
return status
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
// Package md2man aims in converting markdown into roff (man pages).
|
||||
package md2man
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/russross/blackfriday/v2"
|
||||
)
|
||||
|
||||
// Render converts a markdown document into a roff formatted document.
|
||||
func Render(doc []byte) []byte {
|
||||
renderer := NewRoffRenderer()
|
||||
var r blackfriday.Renderer = renderer
|
||||
if v, _ := strconv.ParseBool(os.Getenv("MD2MAN_DEBUG")); v {
|
||||
r = &debugDecorator{Renderer: r}
|
||||
}
|
||||
|
||||
return blackfriday.Run(doc,
|
||||
[]blackfriday.Option{
|
||||
blackfriday.WithRenderer(r),
|
||||
blackfriday.WithExtensions(renderer.GetExtensions()),
|
||||
}...)
|
||||
}
|
||||
-416
@@ -1,416 +0,0 @@
|
||||
package md2man
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/russross/blackfriday/v2"
|
||||
)
|
||||
|
||||
// roffRenderer implements the blackfriday.Renderer interface for creating
|
||||
// roff format (manpages) from markdown text
|
||||
type roffRenderer struct {
|
||||
listCounters []int
|
||||
firstHeader bool
|
||||
listDepth int
|
||||
}
|
||||
|
||||
const (
|
||||
titleHeader = ".TH "
|
||||
topLevelHeader = "\n\n.SH "
|
||||
secondLevelHdr = "\n.SH "
|
||||
otherHeader = "\n.SS "
|
||||
crTag = "\n"
|
||||
emphTag = "\\fI"
|
||||
emphCloseTag = "\\fP"
|
||||
strongTag = "\\fB"
|
||||
strongCloseTag = "\\fP"
|
||||
breakTag = "\n.br\n"
|
||||
paraTag = "\n.PP\n"
|
||||
hruleTag = "\n.ti 0\n\\l'\\n(.lu'\n"
|
||||
linkTag = "\n\\[la]"
|
||||
linkCloseTag = "\\[ra]"
|
||||
codespanTag = "\\fB"
|
||||
codespanCloseTag = "\\fR"
|
||||
codeTag = "\n.EX\n"
|
||||
codeCloseTag = ".EE\n" // Do not prepend a newline character since code blocks, by definition, include a newline already (or at least as how blackfriday gives us on).
|
||||
quoteTag = "\n.PP\n.RS\n"
|
||||
quoteCloseTag = "\n.RE\n"
|
||||
listTag = "\n.RS\n"
|
||||
listCloseTag = ".RE\n"
|
||||
dtTag = "\n.TP\n"
|
||||
dd2Tag = "\n"
|
||||
tableStart = "\n.TS\nallbox;\n"
|
||||
tableEnd = ".TE\n"
|
||||
tableCellStart = "T{\n"
|
||||
tableCellEnd = "\nT}"
|
||||
tablePreprocessor = `'\" t`
|
||||
)
|
||||
|
||||
// NewRoffRenderer creates a new blackfriday Renderer for generating roff documents
|
||||
// from markdown
|
||||
func NewRoffRenderer() *roffRenderer {
|
||||
return &roffRenderer{}
|
||||
}
|
||||
|
||||
// GetExtensions returns the list of extensions used by this renderer implementation
|
||||
func (*roffRenderer) GetExtensions() blackfriday.Extensions {
|
||||
return blackfriday.NoIntraEmphasis |
|
||||
blackfriday.Tables |
|
||||
blackfriday.FencedCode |
|
||||
blackfriday.SpaceHeadings |
|
||||
blackfriday.Footnotes |
|
||||
blackfriday.Titleblock |
|
||||
blackfriday.DefinitionLists
|
||||
}
|
||||
|
||||
// RenderHeader handles outputting the header at document start
|
||||
func (r *roffRenderer) RenderHeader(w io.Writer, ast *blackfriday.Node) {
|
||||
// We need to walk the tree to check if there are any tables.
|
||||
// If there are, we need to enable the roff table preprocessor.
|
||||
ast.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
|
||||
if node.Type == blackfriday.Table {
|
||||
out(w, tablePreprocessor+"\n")
|
||||
return blackfriday.Terminate
|
||||
}
|
||||
return blackfriday.GoToNext
|
||||
})
|
||||
|
||||
// disable hyphenation
|
||||
out(w, ".nh\n")
|
||||
}
|
||||
|
||||
// RenderFooter handles outputting the footer at the document end; the roff
|
||||
// renderer has no footer information
|
||||
func (r *roffRenderer) RenderFooter(w io.Writer, ast *blackfriday.Node) {
|
||||
}
|
||||
|
||||
// RenderNode is called for each node in a markdown document; based on the node
|
||||
// type the equivalent roff output is sent to the writer
|
||||
func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
|
||||
walkAction := blackfriday.GoToNext
|
||||
|
||||
switch node.Type {
|
||||
case blackfriday.Text:
|
||||
// Special case: format the NAME section as required for proper whatis parsing.
|
||||
// Refer to the lexgrog(1) and groff_man(7) manual pages for details.
|
||||
if node.Parent != nil &&
|
||||
node.Parent.Type == blackfriday.Paragraph &&
|
||||
node.Parent.Prev != nil &&
|
||||
node.Parent.Prev.Type == blackfriday.Heading &&
|
||||
node.Parent.Prev.FirstChild != nil &&
|
||||
bytes.EqualFold(node.Parent.Prev.FirstChild.Literal, []byte("NAME")) {
|
||||
before, after, found := bytesCut(node.Literal, []byte(" - "))
|
||||
escapeSpecialChars(w, before)
|
||||
if found {
|
||||
out(w, ` \- `)
|
||||
escapeSpecialChars(w, after)
|
||||
}
|
||||
} else {
|
||||
escapeSpecialChars(w, node.Literal)
|
||||
}
|
||||
case blackfriday.Softbreak:
|
||||
out(w, crTag)
|
||||
case blackfriday.Hardbreak:
|
||||
out(w, breakTag)
|
||||
case blackfriday.Emph:
|
||||
if entering {
|
||||
out(w, emphTag)
|
||||
} else {
|
||||
out(w, emphCloseTag)
|
||||
}
|
||||
case blackfriday.Strong:
|
||||
if entering {
|
||||
out(w, strongTag)
|
||||
} else {
|
||||
out(w, strongCloseTag)
|
||||
}
|
||||
case blackfriday.Link:
|
||||
// Don't render the link text for automatic links, because this
|
||||
// will only duplicate the URL in the roff output.
|
||||
// See https://daringfireball.net/projects/markdown/syntax#autolink
|
||||
if !bytes.Equal(node.LinkData.Destination, node.FirstChild.Literal) {
|
||||
out(w, string(node.FirstChild.Literal))
|
||||
}
|
||||
// Hyphens in a link must be escaped to avoid word-wrap in the rendered man page.
|
||||
escapedLink := strings.ReplaceAll(string(node.LinkData.Destination), "-", "\\-")
|
||||
out(w, linkTag+escapedLink+linkCloseTag)
|
||||
walkAction = blackfriday.SkipChildren
|
||||
case blackfriday.Image:
|
||||
// ignore images
|
||||
walkAction = blackfriday.SkipChildren
|
||||
case blackfriday.Code:
|
||||
out(w, codespanTag)
|
||||
escapeSpecialChars(w, node.Literal)
|
||||
out(w, codespanCloseTag)
|
||||
case blackfriday.Document:
|
||||
break
|
||||
case blackfriday.Paragraph:
|
||||
if entering {
|
||||
if r.listDepth > 0 {
|
||||
// roff .PP markers break lists
|
||||
if node.Prev != nil { // continued paragraph
|
||||
if node.Prev.Type == blackfriday.List && node.Prev.ListFlags&blackfriday.ListTypeDefinition == 0 {
|
||||
out(w, ".IP\n")
|
||||
} else {
|
||||
out(w, crTag)
|
||||
}
|
||||
}
|
||||
} else if node.Prev != nil && node.Prev.Type == blackfriday.Heading {
|
||||
out(w, crTag)
|
||||
} else {
|
||||
out(w, paraTag)
|
||||
}
|
||||
} else {
|
||||
if node.Next == nil || node.Next.Type != blackfriday.List {
|
||||
out(w, crTag)
|
||||
}
|
||||
}
|
||||
case blackfriday.BlockQuote:
|
||||
if entering {
|
||||
out(w, quoteTag)
|
||||
} else {
|
||||
out(w, quoteCloseTag)
|
||||
}
|
||||
case blackfriday.Heading:
|
||||
r.handleHeading(w, node, entering)
|
||||
case blackfriday.HorizontalRule:
|
||||
out(w, hruleTag)
|
||||
case blackfriday.List:
|
||||
r.handleList(w, node, entering)
|
||||
case blackfriday.Item:
|
||||
r.handleItem(w, node, entering)
|
||||
case blackfriday.CodeBlock:
|
||||
out(w, codeTag)
|
||||
escapeSpecialChars(w, node.Literal)
|
||||
out(w, codeCloseTag)
|
||||
case blackfriday.Table:
|
||||
r.handleTable(w, node, entering)
|
||||
case blackfriday.TableHead:
|
||||
case blackfriday.TableBody:
|
||||
case blackfriday.TableRow:
|
||||
// no action as cell entries do all the nroff formatting
|
||||
return blackfriday.GoToNext
|
||||
case blackfriday.TableCell:
|
||||
r.handleTableCell(w, node, entering)
|
||||
case blackfriday.HTMLSpan:
|
||||
// ignore other HTML tags
|
||||
case blackfriday.HTMLBlock:
|
||||
if bytes.HasPrefix(node.Literal, []byte("<!--")) {
|
||||
break // ignore comments, no warning
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
|
||||
}
|
||||
return walkAction
|
||||
}
|
||||
|
||||
func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, entering bool) {
|
||||
if entering {
|
||||
switch node.Level {
|
||||
case 1:
|
||||
if !r.firstHeader {
|
||||
out(w, titleHeader)
|
||||
r.firstHeader = true
|
||||
break
|
||||
}
|
||||
out(w, topLevelHeader)
|
||||
case 2:
|
||||
out(w, secondLevelHdr)
|
||||
default:
|
||||
out(w, otherHeader)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roffRenderer) handleList(w io.Writer, node *blackfriday.Node, entering bool) {
|
||||
openTag := listTag
|
||||
closeTag := listCloseTag
|
||||
if (entering && r.listDepth == 0) || (!entering && r.listDepth == 1) {
|
||||
openTag = crTag
|
||||
closeTag = ""
|
||||
}
|
||||
if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
|
||||
// tags for definition lists handled within Item node
|
||||
openTag = ""
|
||||
closeTag = ""
|
||||
}
|
||||
if entering {
|
||||
r.listDepth++
|
||||
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
|
||||
r.listCounters = append(r.listCounters, 1)
|
||||
}
|
||||
out(w, openTag)
|
||||
} else {
|
||||
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
|
||||
r.listCounters = r.listCounters[:len(r.listCounters)-1]
|
||||
}
|
||||
out(w, closeTag)
|
||||
r.listDepth--
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering bool) {
|
||||
if entering {
|
||||
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
|
||||
out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1]))
|
||||
r.listCounters[len(r.listCounters)-1]++
|
||||
} else if node.ListFlags&blackfriday.ListTypeTerm != 0 {
|
||||
// DT (definition term): line just before DD (see below).
|
||||
out(w, dtTag)
|
||||
} else if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
|
||||
// DD (definition description): line that starts with ": ".
|
||||
//
|
||||
// We have to distinguish between the first DD and the
|
||||
// subsequent ones, as there should be no vertical
|
||||
// whitespace between the DT and the first DD.
|
||||
if node.Prev != nil && node.Prev.ListFlags&(blackfriday.ListTypeTerm|blackfriday.ListTypeDefinition) == blackfriday.ListTypeDefinition {
|
||||
if node.Prev.Type == blackfriday.Item &&
|
||||
node.Prev.LastChild != nil &&
|
||||
node.Prev.LastChild.Type == blackfriday.List &&
|
||||
node.Prev.LastChild.ListFlags&blackfriday.ListTypeDefinition == 0 {
|
||||
out(w, ".IP\n")
|
||||
} else {
|
||||
out(w, dd2Tag)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out(w, ".IP \\(bu 2\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering bool) {
|
||||
if entering {
|
||||
out(w, tableStart)
|
||||
// call walker to count cells (and rows?) so format section can be produced
|
||||
columns := countColumns(node)
|
||||
out(w, strings.Repeat("l ", columns)+"\n")
|
||||
out(w, strings.Repeat("l ", columns)+".\n")
|
||||
} else {
|
||||
out(w, tableEnd)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, entering bool) {
|
||||
if entering {
|
||||
var start string
|
||||
if node.Prev != nil && node.Prev.Type == blackfriday.TableCell {
|
||||
start = "\t"
|
||||
}
|
||||
if node.IsHeader {
|
||||
start += strongTag
|
||||
} else if nodeLiteralSize(node) > 30 {
|
||||
start += tableCellStart
|
||||
}
|
||||
out(w, start)
|
||||
} else {
|
||||
var end string
|
||||
if node.IsHeader {
|
||||
end = strongCloseTag
|
||||
} else if nodeLiteralSize(node) > 30 {
|
||||
end = tableCellEnd
|
||||
}
|
||||
if node.Next == nil {
|
||||
// Last cell: need to carriage return if we are at the end of the header row.
|
||||
end += crTag
|
||||
}
|
||||
out(w, end)
|
||||
}
|
||||
}
|
||||
|
||||
func nodeLiteralSize(node *blackfriday.Node) int {
|
||||
total := 0
|
||||
for n := node.FirstChild; n != nil; n = n.FirstChild {
|
||||
total += len(n.Literal)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// because roff format requires knowing the column count before outputting any table
|
||||
// data we need to walk a table tree and count the columns
|
||||
func countColumns(node *blackfriday.Node) int {
|
||||
var columns int
|
||||
|
||||
node.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
|
||||
switch node.Type {
|
||||
case blackfriday.TableRow:
|
||||
if !entering {
|
||||
return blackfriday.Terminate
|
||||
}
|
||||
case blackfriday.TableCell:
|
||||
if entering {
|
||||
columns++
|
||||
}
|
||||
default:
|
||||
}
|
||||
return blackfriday.GoToNext
|
||||
})
|
||||
return columns
|
||||
}
|
||||
|
||||
func out(w io.Writer, output string) {
|
||||
io.WriteString(w, output) //nolint:errcheck
|
||||
}
|
||||
|
||||
func escapeSpecialChars(w io.Writer, text []byte) {
|
||||
scanner := bufio.NewScanner(bytes.NewReader(text))
|
||||
|
||||
// count the number of lines in the text
|
||||
// we need to know this to avoid adding a newline after the last line
|
||||
n := bytes.Count(text, []byte{'\n'})
|
||||
idx := 0
|
||||
|
||||
for scanner.Scan() {
|
||||
dt := scanner.Bytes()
|
||||
if idx < n {
|
||||
idx++
|
||||
dt = append(dt, '\n')
|
||||
}
|
||||
escapeSpecialCharsLine(w, dt)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func escapeSpecialCharsLine(w io.Writer, text []byte) {
|
||||
for i := 0; i < len(text); i++ {
|
||||
// escape initial apostrophe or period
|
||||
if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') {
|
||||
out(w, "\\&")
|
||||
}
|
||||
|
||||
// directly copy normal characters
|
||||
org := i
|
||||
|
||||
for i < len(text) && text[i] != '\\' {
|
||||
i++
|
||||
}
|
||||
if i > org {
|
||||
w.Write(text[org:i]) //nolint:errcheck
|
||||
}
|
||||
|
||||
// escape a character
|
||||
if i >= len(text) {
|
||||
break
|
||||
}
|
||||
|
||||
w.Write([]byte{'\\', text[i]}) //nolint:errcheck
|
||||
}
|
||||
}
|
||||
|
||||
// bytesCut is a copy of [bytes.Cut] to provide compatibility with go1.17
|
||||
// and older. We can remove this once we drop support for go1.17 and older.
|
||||
func bytesCut(s, sep []byte) (before, after []byte, found bool) {
|
||||
if i := bytes.Index(s, sep); i >= 0 {
|
||||
return s[:i], s[i+len(sep):], true
|
||||
}
|
||||
return s, nil, false
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
*.out
|
||||
*.swp
|
||||
*.8
|
||||
*.6
|
||||
_obj
|
||||
_test*
|
||||
markdown
|
||||
tags
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- "1.10.x"
|
||||
- "1.11.x"
|
||||
- tip
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- go: tip
|
||||
install:
|
||||
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- diff -u <(echo -n) <(gofmt -d -s .)
|
||||
- go tool vet .
|
||||
- go test -v ./...
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
Blackfriday is distributed under the Simplified BSD License:
|
||||
|
||||
> Copyright © 2011 Russ Ross
|
||||
> All rights reserved.
|
||||
>
|
||||
> Redistribution and use in source and binary forms, with or without
|
||||
> modification, are permitted provided that the following conditions
|
||||
> are met:
|
||||
>
|
||||
> 1. Redistributions of source code must retain the above copyright
|
||||
> notice, this list of conditions and the following disclaimer.
|
||||
>
|
||||
> 2. Redistributions in binary form must reproduce the above
|
||||
> copyright notice, this list of conditions and the following
|
||||
> disclaimer in the documentation and/or other materials provided with
|
||||
> the distribution.
|
||||
>
|
||||
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
> FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
> COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
> INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
> BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
> LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
> ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
> POSSIBILITY OF SUCH DAMAGE.
|
||||
-335
@@ -1,335 +0,0 @@
|
||||
Blackfriday
|
||||
[![Build Status][BuildV2SVG]][BuildV2URL]
|
||||
[![PkgGoDev][PkgGoDevV2SVG]][PkgGoDevV2URL]
|
||||
===========
|
||||
|
||||
Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It
|
||||
is paranoid about its input (so you can safely feed it user-supplied
|
||||
data), it is fast, it supports common extensions (tables, smart
|
||||
punctuation substitutions, etc.), and it is safe for all utf-8
|
||||
(unicode) input.
|
||||
|
||||
HTML output is currently supported, along with Smartypants
|
||||
extensions.
|
||||
|
||||
It started as a translation from C of [Sundown][3].
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Blackfriday is compatible with modern Go releases in module mode.
|
||||
With Go installed:
|
||||
|
||||
go get github.com/russross/blackfriday/v2
|
||||
|
||||
will resolve and add the package to the current development module,
|
||||
then build and install it. Alternatively, you can achieve the same
|
||||
if you import it in a package:
|
||||
|
||||
import "github.com/russross/blackfriday/v2"
|
||||
|
||||
and `go get` without parameters.
|
||||
|
||||
Legacy GOPATH mode is unsupported.
|
||||
|
||||
|
||||
Versions
|
||||
--------
|
||||
|
||||
Currently maintained and recommended version of Blackfriday is `v2`. It's being
|
||||
developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the
|
||||
documentation is available at
|
||||
https://pkg.go.dev/github.com/russross/blackfriday/v2.
|
||||
|
||||
It is `go get`-able in module mode at `github.com/russross/blackfriday/v2`.
|
||||
|
||||
Version 2 offers a number of improvements over v1:
|
||||
|
||||
* Cleaned up API
|
||||
* A separate call to [`Parse`][4], which produces an abstract syntax tree for
|
||||
the document
|
||||
* Latest bug fixes
|
||||
* Flexibility to easily add your own rendering extensions
|
||||
|
||||
Potential drawbacks:
|
||||
|
||||
* Our benchmarks show v2 to be slightly slower than v1. Currently in the
|
||||
ballpark of around 15%.
|
||||
* API breakage. If you can't afford modifying your code to adhere to the new API
|
||||
and don't care too much about the new features, v2 is probably not for you.
|
||||
* Several bug fixes are trailing behind and still need to be forward-ported to
|
||||
v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for
|
||||
tracking.
|
||||
|
||||
If you are still interested in the legacy `v1`, you can import it from
|
||||
`github.com/russross/blackfriday`. Documentation for the legacy v1 can be found
|
||||
here: https://pkg.go.dev/github.com/russross/blackfriday.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
For the most sensible markdown processing, it is as simple as getting your input
|
||||
into a byte slice and calling:
|
||||
|
||||
```go
|
||||
output := blackfriday.Run(input)
|
||||
```
|
||||
|
||||
Your input will be parsed and the output rendered with a set of most popular
|
||||
extensions enabled. If you want the most basic feature set, corresponding with
|
||||
the bare Markdown specification, use:
|
||||
|
||||
```go
|
||||
output := blackfriday.Run(input, blackfriday.WithNoExtensions())
|
||||
```
|
||||
|
||||
### Sanitize untrusted content
|
||||
|
||||
Blackfriday itself does nothing to protect against malicious content. If you are
|
||||
dealing with user-supplied markdown, we recommend running Blackfriday's output
|
||||
through HTML sanitizer such as [Bluemonday][5].
|
||||
|
||||
Here's an example of simple usage of Blackfriday together with Bluemonday:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/russross/blackfriday/v2"
|
||||
)
|
||||
|
||||
// ...
|
||||
unsafe := blackfriday.Run(input)
|
||||
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
|
||||
```
|
||||
|
||||
### Custom options
|
||||
|
||||
If you want to customize the set of options, use `blackfriday.WithExtensions`,
|
||||
`blackfriday.WithRenderer` and `blackfriday.WithRefOverride`.
|
||||
|
||||
### `blackfriday-tool`
|
||||
|
||||
You can also check out `blackfriday-tool` for a more complete example
|
||||
of how to use it. Download and install it using:
|
||||
|
||||
go get github.com/russross/blackfriday-tool
|
||||
|
||||
This is a simple command-line tool that allows you to process a
|
||||
markdown file using a standalone program. You can also browse the
|
||||
source directly on github if you are just looking for some example
|
||||
code:
|
||||
|
||||
* <https://github.com/russross/blackfriday-tool>
|
||||
|
||||
Note that if you have not already done so, installing
|
||||
`blackfriday-tool` will be sufficient to download and install
|
||||
blackfriday in addition to the tool itself. The tool binary will be
|
||||
installed in `$GOPATH/bin`. This is a statically-linked binary that
|
||||
can be copied to wherever you need it without worrying about
|
||||
dependencies and library versions.
|
||||
|
||||
### Sanitized anchor names
|
||||
|
||||
Blackfriday includes an algorithm for creating sanitized anchor names
|
||||
corresponding to a given input text. This algorithm is used to create
|
||||
anchors for headings when `AutoHeadingIDs` extension is enabled. The
|
||||
algorithm has a specification, so that other packages can create
|
||||
compatible anchor names and links to those anchors.
|
||||
|
||||
The specification is located at https://pkg.go.dev/github.com/russross/blackfriday/v2#hdr-Sanitized_Anchor_Names.
|
||||
|
||||
[`SanitizedAnchorName`](https://pkg.go.dev/github.com/russross/blackfriday/v2#SanitizedAnchorName) exposes this functionality, and can be used to
|
||||
create compatible links to the anchor names generated by blackfriday.
|
||||
This algorithm is also implemented in a small standalone package at
|
||||
[`github.com/shurcooL/sanitized_anchor_name`](https://pkg.go.dev/github.com/shurcooL/sanitized_anchor_name). It can be useful for clients
|
||||
that want a small package and don't need full functionality of blackfriday.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
All features of Sundown are supported, including:
|
||||
|
||||
* **Compatibility**. The Markdown v1.0.3 test suite passes with
|
||||
the `--tidy` option. Without `--tidy`, the differences are
|
||||
mostly in whitespace and entity escaping, where blackfriday is
|
||||
more consistent and cleaner.
|
||||
|
||||
* **Common extensions**, including table support, fenced code
|
||||
blocks, autolinks, strikethroughs, non-strict emphasis, etc.
|
||||
|
||||
* **Safety**. Blackfriday is paranoid when parsing, making it safe
|
||||
to feed untrusted user input without fear of bad things
|
||||
happening. The test suite stress tests this and there are no
|
||||
known inputs that make it crash. If you find one, please let me
|
||||
know and send me the input that does it.
|
||||
|
||||
NOTE: "safety" in this context means *runtime safety only*. In order to
|
||||
protect yourself against JavaScript injection in untrusted content, see
|
||||
[this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
|
||||
|
||||
* **Fast processing**. It is fast enough to render on-demand in
|
||||
most web applications without having to cache the output.
|
||||
|
||||
* **Thread safety**. You can run multiple parsers in different
|
||||
goroutines without ill effect. There is no dependence on global
|
||||
shared state.
|
||||
|
||||
* **Minimal dependencies**. Blackfriday only depends on standard
|
||||
library packages in Go. The source code is pretty
|
||||
self-contained, so it is easy to add to any project, including
|
||||
Google App Engine projects.
|
||||
|
||||
* **Standards compliant**. Output successfully validates using the
|
||||
W3C validation tool for HTML 4.01 and XHTML 1.0 Transitional.
|
||||
|
||||
|
||||
Extensions
|
||||
----------
|
||||
|
||||
In addition to the standard markdown syntax, this package
|
||||
implements the following extensions:
|
||||
|
||||
* **Intra-word emphasis supression**. The `_` character is
|
||||
commonly used inside words when discussing code, so having
|
||||
markdown interpret it as an emphasis command is usually the
|
||||
wrong thing. Blackfriday lets you treat all emphasis markers as
|
||||
normal characters when they occur inside a word.
|
||||
|
||||
* **Tables**. Tables can be created by drawing them in the input
|
||||
using a simple syntax:
|
||||
|
||||
```
|
||||
Name | Age
|
||||
--------|------
|
||||
Bob | 27
|
||||
Alice | 23
|
||||
```
|
||||
|
||||
* **Fenced code blocks**. In addition to the normal 4-space
|
||||
indentation to mark code blocks, you can explicitly mark them
|
||||
and supply a language (to make syntax highlighting simple). Just
|
||||
mark it like this:
|
||||
|
||||
```go
|
||||
func getTrue() bool {
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
You can use 3 or more backticks to mark the beginning of the
|
||||
block, and the same number to mark the end of the block.
|
||||
|
||||
To preserve classes of fenced code blocks while using the bluemonday
|
||||
HTML sanitizer, use the following policy:
|
||||
|
||||
```go
|
||||
p := bluemonday.UGCPolicy()
|
||||
p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
|
||||
html := p.SanitizeBytes(unsafe)
|
||||
```
|
||||
|
||||
* **Definition lists**. A simple definition list is made of a single-line
|
||||
term followed by a colon and the definition for that term.
|
||||
|
||||
Cat
|
||||
: Fluffy animal everyone likes
|
||||
|
||||
Internet
|
||||
: Vector of transmission for pictures of cats
|
||||
|
||||
Terms must be separated from the previous definition by a blank line.
|
||||
|
||||
* **Footnotes**. A marker in the text that will become a superscript number;
|
||||
a footnote definition that will be placed in a list of footnotes at the
|
||||
end of the document. A footnote looks like this:
|
||||
|
||||
This is a footnote.[^1]
|
||||
|
||||
[^1]: the footnote text.
|
||||
|
||||
* **Autolinking**. Blackfriday can find URLs that have not been
|
||||
explicitly marked as links and turn them into links.
|
||||
|
||||
* **Strikethrough**. Use two tildes (`~~`) to mark text that
|
||||
should be crossed out.
|
||||
|
||||
* **Hard line breaks**. With this extension enabled newlines in the input
|
||||
translate into line breaks in the output. This extension is off by default.
|
||||
|
||||
* **Smart quotes**. Smartypants-style punctuation substitution is
|
||||
supported, turning normal double- and single-quote marks into
|
||||
curly quotes, etc.
|
||||
|
||||
* **LaTeX-style dash parsing** is an additional option, where `--`
|
||||
is translated into `–`, and `---` is translated into
|
||||
`—`. This differs from most smartypants processors, which
|
||||
turn a single hyphen into an ndash and a double hyphen into an
|
||||
mdash.
|
||||
|
||||
* **Smart fractions**, where anything that looks like a fraction
|
||||
is translated into suitable HTML (instead of just a few special
|
||||
cases like most smartypant processors). For example, `4/5`
|
||||
becomes `<sup>4</sup>⁄<sub>5</sub>`, which renders as
|
||||
<sup>4</sup>⁄<sub>5</sub>.
|
||||
|
||||
|
||||
Other renderers
|
||||
---------------
|
||||
|
||||
Blackfriday is structured to allow alternative rendering engines. Here
|
||||
are a few of note:
|
||||
|
||||
* [github_flavored_markdown](https://pkg.go.dev/github.com/shurcooL/github_flavored_markdown):
|
||||
provides a GitHub Flavored Markdown renderer with fenced code block
|
||||
highlighting, clickable heading anchor links.
|
||||
|
||||
It's not customizable, and its goal is to produce HTML output
|
||||
equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode),
|
||||
except the rendering is performed locally.
|
||||
|
||||
* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
|
||||
but for markdown.
|
||||
|
||||
* [LaTeX output](https://gitlab.com/ambrevar/blackfriday-latex):
|
||||
renders output as LaTeX.
|
||||
|
||||
* [bfchroma](https://github.com/Depado/bfchroma/): provides convenience
|
||||
integration with the [Chroma](https://github.com/alecthomas/chroma) code
|
||||
highlighting library. bfchroma is only compatible with v2 of Blackfriday and
|
||||
provides a drop-in renderer ready to use with Blackfriday, as well as
|
||||
options and means for further customization.
|
||||
|
||||
* [Blackfriday-Confluence](https://github.com/kentaro-m/blackfriday-confluence): provides a [Confluence Wiki Markup](https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html) renderer.
|
||||
|
||||
* [Blackfriday-Slack](https://github.com/karriereat/blackfriday-slack): converts markdown to slack message style
|
||||
|
||||
|
||||
TODO
|
||||
----
|
||||
|
||||
* More unit testing
|
||||
* Improve Unicode support. It does not understand all Unicode
|
||||
rules (about what constitutes a letter, a punctuation symbol,
|
||||
etc.), so it may fail to detect word boundaries correctly in
|
||||
some instances. It is safe on all UTF-8 input.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
[Blackfriday is distributed under the Simplified BSD License](LICENSE.txt)
|
||||
|
||||
|
||||
[1]: https://daringfireball.net/projects/markdown/ "Markdown"
|
||||
[2]: https://golang.org/ "Go Language"
|
||||
[3]: https://github.com/vmg/sundown "Sundown"
|
||||
[4]: https://pkg.go.dev/github.com/russross/blackfriday/v2#Parse "Parse func"
|
||||
[5]: https://github.com/microcosm-cc/bluemonday "Bluemonday"
|
||||
|
||||
[BuildV2SVG]: https://travis-ci.org/russross/blackfriday.svg?branch=v2
|
||||
[BuildV2URL]: https://travis-ci.org/russross/blackfriday
|
||||
[PkgGoDevV2SVG]: https://pkg.go.dev/badge/github.com/russross/blackfriday/v2
|
||||
[PkgGoDevV2URL]: https://pkg.go.dev/github.com/russross/blackfriday/v2
|
||||
-1612
File diff suppressed because it is too large
Load Diff
-46
@@ -1,46 +0,0 @@
|
||||
// Package blackfriday is a markdown processor.
|
||||
//
|
||||
// It translates plain text with simple formatting rules into an AST, which can
|
||||
// then be further processed to HTML (provided by Blackfriday itself) or other
|
||||
// formats (provided by the community).
|
||||
//
|
||||
// The simplest way to invoke Blackfriday is to call the Run function. It will
|
||||
// take a text input and produce a text output in HTML (or other format).
|
||||
//
|
||||
// A slightly more sophisticated way to use Blackfriday is to create a Markdown
|
||||
// processor and to call Parse, which returns a syntax tree for the input
|
||||
// document. You can leverage Blackfriday's parsing for content extraction from
|
||||
// markdown documents. You can assign a custom renderer and set various options
|
||||
// to the Markdown processor.
|
||||
//
|
||||
// If you're interested in calling Blackfriday from command line, see
|
||||
// https://github.com/russross/blackfriday-tool.
|
||||
//
|
||||
// Sanitized Anchor Names
|
||||
//
|
||||
// Blackfriday includes an algorithm for creating sanitized anchor names
|
||||
// corresponding to a given input text. This algorithm is used to create
|
||||
// anchors for headings when AutoHeadingIDs extension is enabled. The
|
||||
// algorithm is specified below, so that other packages can create
|
||||
// compatible anchor names and links to those anchors.
|
||||
//
|
||||
// The algorithm iterates over the input text, interpreted as UTF-8,
|
||||
// one Unicode code point (rune) at a time. All runes that are letters (category L)
|
||||
// or numbers (category N) are considered valid characters. They are mapped to
|
||||
// lower case, and included in the output. All other runes are considered
|
||||
// invalid characters. Invalid characters that precede the first valid character,
|
||||
// as well as invalid character that follow the last valid character
|
||||
// are dropped completely. All other sequences of invalid characters
|
||||
// between two valid characters are replaced with a single dash character '-'.
|
||||
//
|
||||
// SanitizedAnchorName exposes this functionality, and can be used to
|
||||
// create compatible links to the anchor names generated by blackfriday.
|
||||
// This algorithm is also implemented in a small standalone package at
|
||||
// github.com/shurcooL/sanitized_anchor_name. It can be useful for clients
|
||||
// that want a small package and don't need full functionality of blackfriday.
|
||||
package blackfriday
|
||||
|
||||
// NOTE: Keep Sanitized Anchor Name algorithm in sync with package
|
||||
// github.com/shurcooL/sanitized_anchor_name.
|
||||
// Otherwise, users of sanitized_anchor_name will get anchor names
|
||||
// that are incompatible with those generated by blackfriday.
|
||||
-2236
File diff suppressed because it is too large
Load Diff
-70
@@ -1,70 +0,0 @@
|
||||
package blackfriday
|
||||
|
||||
import (
|
||||
"html"
|
||||
"io"
|
||||
)
|
||||
|
||||
var htmlEscaper = [256][]byte{
|
||||
'&': []byte("&"),
|
||||
'<': []byte("<"),
|
||||
'>': []byte(">"),
|
||||
'"': []byte("""),
|
||||
}
|
||||
|
||||
func escapeHTML(w io.Writer, s []byte) {
|
||||
escapeEntities(w, s, false)
|
||||
}
|
||||
|
||||
func escapeAllHTML(w io.Writer, s []byte) {
|
||||
escapeEntities(w, s, true)
|
||||
}
|
||||
|
||||
func escapeEntities(w io.Writer, s []byte, escapeValidEntities bool) {
|
||||
var start, end int
|
||||
for end < len(s) {
|
||||
escSeq := htmlEscaper[s[end]]
|
||||
if escSeq != nil {
|
||||
isEntity, entityEnd := nodeIsEntity(s, end)
|
||||
if isEntity && !escapeValidEntities {
|
||||
w.Write(s[start : entityEnd+1])
|
||||
start = entityEnd + 1
|
||||
} else {
|
||||
w.Write(s[start:end])
|
||||
w.Write(escSeq)
|
||||
start = end + 1
|
||||
}
|
||||
}
|
||||
end++
|
||||
}
|
||||
if start < len(s) && end <= len(s) {
|
||||
w.Write(s[start:end])
|
||||
}
|
||||
}
|
||||
|
||||
func nodeIsEntity(s []byte, end int) (isEntity bool, endEntityPos int) {
|
||||
isEntity = false
|
||||
endEntityPos = end + 1
|
||||
|
||||
if s[end] == '&' {
|
||||
for endEntityPos < len(s) {
|
||||
if s[endEntityPos] == ';' {
|
||||
if entities[string(s[end:endEntityPos+1])] {
|
||||
isEntity = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isalnum(s[endEntityPos]) && s[endEntityPos] != '&' && s[endEntityPos] != '#' {
|
||||
break
|
||||
}
|
||||
endEntityPos++
|
||||
}
|
||||
}
|
||||
|
||||
return isEntity, endEntityPos
|
||||
}
|
||||
|
||||
func escLink(w io.Writer, text []byte) {
|
||||
unesc := html.UnescapeString(string(text))
|
||||
escapeHTML(w, []byte(unesc))
|
||||
}
|
||||
-952
@@ -1,952 +0,0 @@
|
||||
//
|
||||
// Blackfriday Markdown Processor
|
||||
// Available at http://github.com/russross/blackfriday
|
||||
//
|
||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||
// Distributed under the Simplified BSD License.
|
||||
// See README.md for details.
|
||||
//
|
||||
|
||||
//
|
||||
//
|
||||
// HTML rendering backend
|
||||
//
|
||||
//
|
||||
|
||||
package blackfriday
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HTMLFlags control optional behavior of HTML renderer.
|
||||
type HTMLFlags int
|
||||
|
||||
// HTML renderer configuration options.
|
||||
const (
|
||||
HTMLFlagsNone HTMLFlags = 0
|
||||
SkipHTML HTMLFlags = 1 << iota // Skip preformatted HTML blocks
|
||||
SkipImages // Skip embedded images
|
||||
SkipLinks // Skip all links
|
||||
Safelink // Only link to trusted protocols
|
||||
NofollowLinks // Only link with rel="nofollow"
|
||||
NoreferrerLinks // Only link with rel="noreferrer"
|
||||
NoopenerLinks // Only link with rel="noopener"
|
||||
HrefTargetBlank // Add a blank target
|
||||
CompletePage // Generate a complete HTML page
|
||||
UseXHTML // Generate XHTML output instead of HTML
|
||||
FootnoteReturnLinks // Generate a link at the end of a footnote to return to the source
|
||||
Smartypants // Enable smart punctuation substitutions
|
||||
SmartypantsFractions // Enable smart fractions (with Smartypants)
|
||||
SmartypantsDashes // Enable smart dashes (with Smartypants)
|
||||
SmartypantsLatexDashes // Enable LaTeX-style dashes (with Smartypants)
|
||||
SmartypantsAngledQuotes // Enable angled double quotes (with Smartypants) for double quotes rendering
|
||||
SmartypantsQuotesNBSP // Enable « French guillemets » (with Smartypants)
|
||||
TOC // Generate a table of contents
|
||||
)
|
||||
|
||||
var (
|
||||
htmlTagRe = regexp.MustCompile("(?i)^" + htmlTag)
|
||||
)
|
||||
|
||||
const (
|
||||
htmlTag = "(?:" + openTag + "|" + closeTag + "|" + htmlComment + "|" +
|
||||
processingInstruction + "|" + declaration + "|" + cdata + ")"
|
||||
closeTag = "</" + tagName + "\\s*[>]"
|
||||
openTag = "<" + tagName + attribute + "*" + "\\s*/?>"
|
||||
attribute = "(?:" + "\\s+" + attributeName + attributeValueSpec + "?)"
|
||||
attributeValue = "(?:" + unquotedValue + "|" + singleQuotedValue + "|" + doubleQuotedValue + ")"
|
||||
attributeValueSpec = "(?:" + "\\s*=" + "\\s*" + attributeValue + ")"
|
||||
attributeName = "[a-zA-Z_:][a-zA-Z0-9:._-]*"
|
||||
cdata = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>"
|
||||
declaration = "<![A-Z]+" + "\\s+[^>]*>"
|
||||
doubleQuotedValue = "\"[^\"]*\""
|
||||
htmlComment = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->"
|
||||
processingInstruction = "[<][?].*?[?][>]"
|
||||
singleQuotedValue = "'[^']*'"
|
||||
tagName = "[A-Za-z][A-Za-z0-9-]*"
|
||||
unquotedValue = "[^\"'=<>`\\x00-\\x20]+"
|
||||
)
|
||||
|
||||
// HTMLRendererParameters is a collection of supplementary parameters tweaking
|
||||
// the behavior of various parts of HTML renderer.
|
||||
type HTMLRendererParameters struct {
|
||||
// Prepend this text to each relative URL.
|
||||
AbsolutePrefix string
|
||||
// Add this text to each footnote anchor, to ensure uniqueness.
|
||||
FootnoteAnchorPrefix string
|
||||
// Show this text inside the <a> tag for a footnote return link, if the
|
||||
// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
|
||||
// <sup>[return]</sup> is used.
|
||||
FootnoteReturnLinkContents string
|
||||
// If set, add this text to the front of each Heading ID, to ensure
|
||||
// uniqueness.
|
||||
HeadingIDPrefix string
|
||||
// If set, add this text to the back of each Heading ID, to ensure uniqueness.
|
||||
HeadingIDSuffix string
|
||||
// Increase heading levels: if the offset is 1, <h1> becomes <h2> etc.
|
||||
// Negative offset is also valid.
|
||||
// Resulting levels are clipped between 1 and 6.
|
||||
HeadingLevelOffset int
|
||||
|
||||
Title string // Document title (used if CompletePage is set)
|
||||
CSS string // Optional CSS file URL (used if CompletePage is set)
|
||||
Icon string // Optional icon file URL (used if CompletePage is set)
|
||||
|
||||
Flags HTMLFlags // Flags allow customizing this renderer's behavior
|
||||
}
|
||||
|
||||
// HTMLRenderer is a type that implements the Renderer interface for HTML output.
|
||||
//
|
||||
// Do not create this directly, instead use the NewHTMLRenderer function.
|
||||
type HTMLRenderer struct {
|
||||
HTMLRendererParameters
|
||||
|
||||
closeTag string // how to end singleton tags: either " />" or ">"
|
||||
|
||||
// Track heading IDs to prevent ID collision in a single generation.
|
||||
headingIDs map[string]int
|
||||
|
||||
lastOutputLen int
|
||||
disableTags int
|
||||
|
||||
sr *SPRenderer
|
||||
}
|
||||
|
||||
const (
|
||||
xhtmlClose = " />"
|
||||
htmlClose = ">"
|
||||
)
|
||||
|
||||
// NewHTMLRenderer creates and configures an HTMLRenderer object, which
|
||||
// satisfies the Renderer interface.
|
||||
func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer {
|
||||
// configure the rendering engine
|
||||
closeTag := htmlClose
|
||||
if params.Flags&UseXHTML != 0 {
|
||||
closeTag = xhtmlClose
|
||||
}
|
||||
|
||||
if params.FootnoteReturnLinkContents == "" {
|
||||
// U+FE0E is VARIATION SELECTOR-15.
|
||||
// It suppresses automatic emoji presentation of the preceding
|
||||
// U+21A9 LEFTWARDS ARROW WITH HOOK on iOS and iPadOS.
|
||||
params.FootnoteReturnLinkContents = "<span aria-label='Return'>↩\ufe0e</span>"
|
||||
}
|
||||
|
||||
return &HTMLRenderer{
|
||||
HTMLRendererParameters: params,
|
||||
|
||||
closeTag: closeTag,
|
||||
headingIDs: make(map[string]int),
|
||||
|
||||
sr: NewSmartypantsRenderer(params.Flags),
|
||||
}
|
||||
}
|
||||
|
||||
func isHTMLTag(tag []byte, tagname string) bool {
|
||||
found, _ := findHTMLTagPos(tag, tagname)
|
||||
return found
|
||||
}
|
||||
|
||||
// Look for a character, but ignore it when it's in any kind of quotes, it
|
||||
// might be JavaScript
|
||||
func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
|
||||
inSingleQuote := false
|
||||
inDoubleQuote := false
|
||||
inGraveQuote := false
|
||||
i := start
|
||||
for i < len(html) {
|
||||
switch {
|
||||
case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
|
||||
return i
|
||||
case html[i] == '\'':
|
||||
inSingleQuote = !inSingleQuote
|
||||
case html[i] == '"':
|
||||
inDoubleQuote = !inDoubleQuote
|
||||
case html[i] == '`':
|
||||
inGraveQuote = !inGraveQuote
|
||||
}
|
||||
i++
|
||||
}
|
||||
return start
|
||||
}
|
||||
|
||||
func findHTMLTagPos(tag []byte, tagname string) (bool, int) {
|
||||
i := 0
|
||||
if i < len(tag) && tag[0] != '<' {
|
||||
return false, -1
|
||||
}
|
||||
i++
|
||||
i = skipSpace(tag, i)
|
||||
|
||||
if i < len(tag) && tag[i] == '/' {
|
||||
i++
|
||||
}
|
||||
|
||||
i = skipSpace(tag, i)
|
||||
j := 0
|
||||
for ; i < len(tag); i, j = i+1, j+1 {
|
||||
if j >= len(tagname) {
|
||||
break
|
||||
}
|
||||
|
||||
if strings.ToLower(string(tag[i]))[0] != tagname[j] {
|
||||
return false, -1
|
||||
}
|
||||
}
|
||||
|
||||
if i == len(tag) {
|
||||
return false, -1
|
||||
}
|
||||
|
||||
rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
|
||||
if rightAngle >= i {
|
||||
return true, rightAngle
|
||||
}
|
||||
|
||||
return false, -1
|
||||
}
|
||||
|
||||
func skipSpace(tag []byte, i int) int {
|
||||
for i < len(tag) && isspace(tag[i]) {
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func isRelativeLink(link []byte) (yes bool) {
|
||||
// a tag begin with '#'
|
||||
if link[0] == '#' {
|
||||
return true
|
||||
}
|
||||
|
||||
// link begin with '/' but not '//', the second maybe a protocol relative link
|
||||
if len(link) >= 2 && link[0] == '/' && link[1] != '/' {
|
||||
return true
|
||||
}
|
||||
|
||||
// only the root '/'
|
||||
if len(link) == 1 && link[0] == '/' {
|
||||
return true
|
||||
}
|
||||
|
||||
// current directory : begin with "./"
|
||||
if bytes.HasPrefix(link, []byte("./")) {
|
||||
return true
|
||||
}
|
||||
|
||||
// parent directory : begin with "../"
|
||||
if bytes.HasPrefix(link, []byte("../")) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *HTMLRenderer) ensureUniqueHeadingID(id string) string {
|
||||
for count, found := r.headingIDs[id]; found; count, found = r.headingIDs[id] {
|
||||
tmp := fmt.Sprintf("%s-%d", id, count+1)
|
||||
|
||||
if _, tmpFound := r.headingIDs[tmp]; !tmpFound {
|
||||
r.headingIDs[id] = count + 1
|
||||
id = tmp
|
||||
} else {
|
||||
id = id + "-1"
|
||||
}
|
||||
}
|
||||
|
||||
if _, found := r.headingIDs[id]; !found {
|
||||
r.headingIDs[id] = 0
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func (r *HTMLRenderer) addAbsPrefix(link []byte) []byte {
|
||||
if r.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
|
||||
newDest := r.AbsolutePrefix
|
||||
if link[0] != '/' {
|
||||
newDest += "/"
|
||||
}
|
||||
newDest += string(link)
|
||||
return []byte(newDest)
|
||||
}
|
||||
return link
|
||||
}
|
||||
|
||||
func appendLinkAttrs(attrs []string, flags HTMLFlags, link []byte) []string {
|
||||
if isRelativeLink(link) {
|
||||
return attrs
|
||||
}
|
||||
val := []string{}
|
||||
if flags&NofollowLinks != 0 {
|
||||
val = append(val, "nofollow")
|
||||
}
|
||||
if flags&NoreferrerLinks != 0 {
|
||||
val = append(val, "noreferrer")
|
||||
}
|
||||
if flags&NoopenerLinks != 0 {
|
||||
val = append(val, "noopener")
|
||||
}
|
||||
if flags&HrefTargetBlank != 0 {
|
||||
attrs = append(attrs, "target=\"_blank\"")
|
||||
}
|
||||
if len(val) == 0 {
|
||||
return attrs
|
||||
}
|
||||
attr := fmt.Sprintf("rel=%q", strings.Join(val, " "))
|
||||
return append(attrs, attr)
|
||||
}
|
||||
|
||||
func isMailto(link []byte) bool {
|
||||
return bytes.HasPrefix(link, []byte("mailto:"))
|
||||
}
|
||||
|
||||
func needSkipLink(flags HTMLFlags, dest []byte) bool {
|
||||
if flags&SkipLinks != 0 {
|
||||
return true
|
||||
}
|
||||
return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest)
|
||||
}
|
||||
|
||||
func isSmartypantable(node *Node) bool {
|
||||
pt := node.Parent.Type
|
||||
return pt != Link && pt != CodeBlock && pt != Code
|
||||
}
|
||||
|
||||
func appendLanguageAttr(attrs []string, info []byte) []string {
|
||||
if len(info) == 0 {
|
||||
return attrs
|
||||
}
|
||||
endOfLang := bytes.IndexAny(info, "\t ")
|
||||
if endOfLang < 0 {
|
||||
endOfLang = len(info)
|
||||
}
|
||||
return append(attrs, fmt.Sprintf("class=\"language-%s\"", info[:endOfLang]))
|
||||
}
|
||||
|
||||
func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) {
|
||||
w.Write(name)
|
||||
if len(attrs) > 0 {
|
||||
w.Write(spaceBytes)
|
||||
w.Write([]byte(strings.Join(attrs, " ")))
|
||||
}
|
||||
w.Write(gtBytes)
|
||||
r.lastOutputLen = 1
|
||||
}
|
||||
|
||||
func footnoteRef(prefix string, node *Node) []byte {
|
||||
urlFrag := prefix + string(slugify(node.Destination))
|
||||
anchor := fmt.Sprintf(`<a href="#fn:%s">%d</a>`, urlFrag, node.NoteID)
|
||||
return []byte(fmt.Sprintf(`<sup class="footnote-ref" id="fnref:%s">%s</sup>`, urlFrag, anchor))
|
||||
}
|
||||
|
||||
func footnoteItem(prefix string, slug []byte) []byte {
|
||||
return []byte(fmt.Sprintf(`<li id="fn:%s%s">`, prefix, slug))
|
||||
}
|
||||
|
||||
func footnoteReturnLink(prefix, returnLink string, slug []byte) []byte {
|
||||
const format = ` <a class="footnote-return" href="#fnref:%s%s">%s</a>`
|
||||
return []byte(fmt.Sprintf(format, prefix, slug, returnLink))
|
||||
}
|
||||
|
||||
func itemOpenCR(node *Node) bool {
|
||||
if node.Prev == nil {
|
||||
return false
|
||||
}
|
||||
ld := node.Parent.ListData
|
||||
return !ld.Tight && ld.ListFlags&ListTypeDefinition == 0
|
||||
}
|
||||
|
||||
func skipParagraphTags(node *Node) bool {
|
||||
grandparent := node.Parent.Parent
|
||||
if grandparent == nil || grandparent.Type != List {
|
||||
return false
|
||||
}
|
||||
tightOrTerm := grandparent.Tight || node.Parent.ListFlags&ListTypeTerm != 0
|
||||
return grandparent.Type == List && tightOrTerm
|
||||
}
|
||||
|
||||
func cellAlignment(align CellAlignFlags) string {
|
||||
switch align {
|
||||
case TableAlignmentLeft:
|
||||
return "left"
|
||||
case TableAlignmentRight:
|
||||
return "right"
|
||||
case TableAlignmentCenter:
|
||||
return "center"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (r *HTMLRenderer) out(w io.Writer, text []byte) {
|
||||
if r.disableTags > 0 {
|
||||
w.Write(htmlTagRe.ReplaceAll(text, []byte{}))
|
||||
} else {
|
||||
w.Write(text)
|
||||
}
|
||||
r.lastOutputLen = len(text)
|
||||
}
|
||||
|
||||
func (r *HTMLRenderer) cr(w io.Writer) {
|
||||
if r.lastOutputLen > 0 {
|
||||
r.out(w, nlBytes)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
nlBytes = []byte{'\n'}
|
||||
gtBytes = []byte{'>'}
|
||||
spaceBytes = []byte{' '}
|
||||
)
|
||||
|
||||
var (
|
||||
brTag = []byte("<br>")
|
||||
brXHTMLTag = []byte("<br />")
|
||||
emTag = []byte("<em>")
|
||||
emCloseTag = []byte("</em>")
|
||||
strongTag = []byte("<strong>")
|
||||
strongCloseTag = []byte("</strong>")
|
||||
delTag = []byte("<del>")
|
||||
delCloseTag = []byte("</del>")
|
||||
ttTag = []byte("<tt>")
|
||||
ttCloseTag = []byte("</tt>")
|
||||
aTag = []byte("<a")
|
||||
aCloseTag = []byte("</a>")
|
||||
preTag = []byte("<pre>")
|
||||
preCloseTag = []byte("</pre>")
|
||||
codeTag = []byte("<code>")
|
||||
codeCloseTag = []byte("</code>")
|
||||
pTag = []byte("<p>")
|
||||
pCloseTag = []byte("</p>")
|
||||
blockquoteTag = []byte("<blockquote>")
|
||||
blockquoteCloseTag = []byte("</blockquote>")
|
||||
hrTag = []byte("<hr>")
|
||||
hrXHTMLTag = []byte("<hr />")
|
||||
ulTag = []byte("<ul>")
|
||||
ulCloseTag = []byte("</ul>")
|
||||
olTag = []byte("<ol>")
|
||||
olCloseTag = []byte("</ol>")
|
||||
dlTag = []byte("<dl>")
|
||||
dlCloseTag = []byte("</dl>")
|
||||
liTag = []byte("<li>")
|
||||
liCloseTag = []byte("</li>")
|
||||
ddTag = []byte("<dd>")
|
||||
ddCloseTag = []byte("</dd>")
|
||||
dtTag = []byte("<dt>")
|
||||
dtCloseTag = []byte("</dt>")
|
||||
tableTag = []byte("<table>")
|
||||
tableCloseTag = []byte("</table>")
|
||||
tdTag = []byte("<td")
|
||||
tdCloseTag = []byte("</td>")
|
||||
thTag = []byte("<th")
|
||||
thCloseTag = []byte("</th>")
|
||||
theadTag = []byte("<thead>")
|
||||
theadCloseTag = []byte("</thead>")
|
||||
tbodyTag = []byte("<tbody>")
|
||||
tbodyCloseTag = []byte("</tbody>")
|
||||
trTag = []byte("<tr>")
|
||||
trCloseTag = []byte("</tr>")
|
||||
h1Tag = []byte("<h1")
|
||||
h1CloseTag = []byte("</h1>")
|
||||
h2Tag = []byte("<h2")
|
||||
h2CloseTag = []byte("</h2>")
|
||||
h3Tag = []byte("<h3")
|
||||
h3CloseTag = []byte("</h3>")
|
||||
h4Tag = []byte("<h4")
|
||||
h4CloseTag = []byte("</h4>")
|
||||
h5Tag = []byte("<h5")
|
||||
h5CloseTag = []byte("</h5>")
|
||||
h6Tag = []byte("<h6")
|
||||
h6CloseTag = []byte("</h6>")
|
||||
|
||||
footnotesDivBytes = []byte("\n<div class=\"footnotes\">\n\n")
|
||||
footnotesCloseDivBytes = []byte("\n</div>\n")
|
||||
)
|
||||
|
||||
func headingTagsFromLevel(level int) ([]byte, []byte) {
|
||||
if level <= 1 {
|
||||
return h1Tag, h1CloseTag
|
||||
}
|
||||
switch level {
|
||||
case 2:
|
||||
return h2Tag, h2CloseTag
|
||||
case 3:
|
||||
return h3Tag, h3CloseTag
|
||||
case 4:
|
||||
return h4Tag, h4CloseTag
|
||||
case 5:
|
||||
return h5Tag, h5CloseTag
|
||||
}
|
||||
return h6Tag, h6CloseTag
|
||||
}
|
||||
|
||||
func (r *HTMLRenderer) outHRTag(w io.Writer) {
|
||||
if r.Flags&UseXHTML == 0 {
|
||||
r.out(w, hrTag)
|
||||
} else {
|
||||
r.out(w, hrXHTMLTag)
|
||||
}
|
||||
}
|
||||
|
||||
// RenderNode is a default renderer of a single node of a syntax tree. For
|
||||
// block nodes it will be called twice: first time with entering=true, second
|
||||
// time with entering=false, so that it could know when it's working on an open
|
||||
// tag and when on close. It writes the result to w.
|
||||
//
|
||||
// The return value is a way to tell the calling walker to adjust its walk
|
||||
// pattern: e.g. it can terminate the traversal by returning Terminate. Or it
|
||||
// can ask the walker to skip a subtree of this node by returning SkipChildren.
|
||||
// The typical behavior is to return GoToNext, which asks for the usual
|
||||
// traversal to the next node.
|
||||
func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus {
|
||||
attrs := []string{}
|
||||
switch node.Type {
|
||||
case Text:
|
||||
if r.Flags&Smartypants != 0 {
|
||||
var tmp bytes.Buffer
|
||||
escapeHTML(&tmp, node.Literal)
|
||||
r.sr.Process(w, tmp.Bytes())
|
||||
} else {
|
||||
if node.Parent.Type == Link {
|
||||
escLink(w, node.Literal)
|
||||
} else {
|
||||
escapeHTML(w, node.Literal)
|
||||
}
|
||||
}
|
||||
case Softbreak:
|
||||
r.cr(w)
|
||||
// TODO: make it configurable via out(renderer.softbreak)
|
||||
case Hardbreak:
|
||||
if r.Flags&UseXHTML == 0 {
|
||||
r.out(w, brTag)
|
||||
} else {
|
||||
r.out(w, brXHTMLTag)
|
||||
}
|
||||
r.cr(w)
|
||||
case Emph:
|
||||
if entering {
|
||||
r.out(w, emTag)
|
||||
} else {
|
||||
r.out(w, emCloseTag)
|
||||
}
|
||||
case Strong:
|
||||
if entering {
|
||||
r.out(w, strongTag)
|
||||
} else {
|
||||
r.out(w, strongCloseTag)
|
||||
}
|
||||
case Del:
|
||||
if entering {
|
||||
r.out(w, delTag)
|
||||
} else {
|
||||
r.out(w, delCloseTag)
|
||||
}
|
||||
case HTMLSpan:
|
||||
if r.Flags&SkipHTML != 0 {
|
||||
break
|
||||
}
|
||||
r.out(w, node.Literal)
|
||||
case Link:
|
||||
// mark it but don't link it if it is not a safe link: no smartypants
|
||||
dest := node.LinkData.Destination
|
||||
if needSkipLink(r.Flags, dest) {
|
||||
if entering {
|
||||
r.out(w, ttTag)
|
||||
} else {
|
||||
r.out(w, ttCloseTag)
|
||||
}
|
||||
} else {
|
||||
if entering {
|
||||
dest = r.addAbsPrefix(dest)
|
||||
var hrefBuf bytes.Buffer
|
||||
hrefBuf.WriteString("href=\"")
|
||||
escLink(&hrefBuf, dest)
|
||||
hrefBuf.WriteByte('"')
|
||||
attrs = append(attrs, hrefBuf.String())
|
||||
if node.NoteID != 0 {
|
||||
r.out(w, footnoteRef(r.FootnoteAnchorPrefix, node))
|
||||
break
|
||||
}
|
||||
attrs = appendLinkAttrs(attrs, r.Flags, dest)
|
||||
if len(node.LinkData.Title) > 0 {
|
||||
var titleBuff bytes.Buffer
|
||||
titleBuff.WriteString("title=\"")
|
||||
escapeHTML(&titleBuff, node.LinkData.Title)
|
||||
titleBuff.WriteByte('"')
|
||||
attrs = append(attrs, titleBuff.String())
|
||||
}
|
||||
r.tag(w, aTag, attrs)
|
||||
} else {
|
||||
if node.NoteID != 0 {
|
||||
break
|
||||
}
|
||||
r.out(w, aCloseTag)
|
||||
}
|
||||
}
|
||||
case Image:
|
||||
if r.Flags&SkipImages != 0 {
|
||||
return SkipChildren
|
||||
}
|
||||
if entering {
|
||||
dest := node.LinkData.Destination
|
||||
dest = r.addAbsPrefix(dest)
|
||||
if r.disableTags == 0 {
|
||||
//if options.safe && potentiallyUnsafe(dest) {
|
||||
//out(w, `<img src="" alt="`)
|
||||
//} else {
|
||||
r.out(w, []byte(`<img src="`))
|
||||
escLink(w, dest)
|
||||
r.out(w, []byte(`" alt="`))
|
||||
//}
|
||||
}
|
||||
r.disableTags++
|
||||
} else {
|
||||
r.disableTags--
|
||||
if r.disableTags == 0 {
|
||||
if node.LinkData.Title != nil {
|
||||
r.out(w, []byte(`" title="`))
|
||||
escapeHTML(w, node.LinkData.Title)
|
||||
}
|
||||
r.out(w, []byte(`" />`))
|
||||
}
|
||||
}
|
||||
case Code:
|
||||
r.out(w, codeTag)
|
||||
escapeAllHTML(w, node.Literal)
|
||||
r.out(w, codeCloseTag)
|
||||
case Document:
|
||||
break
|
||||
case Paragraph:
|
||||
if skipParagraphTags(node) {
|
||||
break
|
||||
}
|
||||
if entering {
|
||||
// TODO: untangle this clusterfuck about when the newlines need
|
||||
// to be added and when not.
|
||||
if node.Prev != nil {
|
||||
switch node.Prev.Type {
|
||||
case HTMLBlock, List, Paragraph, Heading, CodeBlock, BlockQuote, HorizontalRule:
|
||||
r.cr(w)
|
||||
}
|
||||
}
|
||||
if node.Parent.Type == BlockQuote && node.Prev == nil {
|
||||
r.cr(w)
|
||||
}
|
||||
r.out(w, pTag)
|
||||
} else {
|
||||
r.out(w, pCloseTag)
|
||||
if !(node.Parent.Type == Item && node.Next == nil) {
|
||||
r.cr(w)
|
||||
}
|
||||
}
|
||||
case BlockQuote:
|
||||
if entering {
|
||||
r.cr(w)
|
||||
r.out(w, blockquoteTag)
|
||||
} else {
|
||||
r.out(w, blockquoteCloseTag)
|
||||
r.cr(w)
|
||||
}
|
||||
case HTMLBlock:
|
||||
if r.Flags&SkipHTML != 0 {
|
||||
break
|
||||
}
|
||||
r.cr(w)
|
||||
r.out(w, node.Literal)
|
||||
r.cr(w)
|
||||
case Heading:
|
||||
headingLevel := r.HTMLRendererParameters.HeadingLevelOffset + node.Level
|
||||
openTag, closeTag := headingTagsFromLevel(headingLevel)
|
||||
if entering {
|
||||
if node.IsTitleblock {
|
||||
attrs = append(attrs, `class="title"`)
|
||||
}
|
||||
if node.HeadingID != "" {
|
||||
id := r.ensureUniqueHeadingID(node.HeadingID)
|
||||
if r.HeadingIDPrefix != "" {
|
||||
id = r.HeadingIDPrefix + id
|
||||
}
|
||||
if r.HeadingIDSuffix != "" {
|
||||
id = id + r.HeadingIDSuffix
|
||||
}
|
||||
attrs = append(attrs, fmt.Sprintf(`id="%s"`, id))
|
||||
}
|
||||
r.cr(w)
|
||||
r.tag(w, openTag, attrs)
|
||||
} else {
|
||||
r.out(w, closeTag)
|
||||
if !(node.Parent.Type == Item && node.Next == nil) {
|
||||
r.cr(w)
|
||||
}
|
||||
}
|
||||
case HorizontalRule:
|
||||
r.cr(w)
|
||||
r.outHRTag(w)
|
||||
r.cr(w)
|
||||
case List:
|
||||
openTag := ulTag
|
||||
closeTag := ulCloseTag
|
||||
if node.ListFlags&ListTypeOrdered != 0 {
|
||||
openTag = olTag
|
||||
closeTag = olCloseTag
|
||||
}
|
||||
if node.ListFlags&ListTypeDefinition != 0 {
|
||||
openTag = dlTag
|
||||
closeTag = dlCloseTag
|
||||
}
|
||||
if entering {
|
||||
if node.IsFootnotesList {
|
||||
r.out(w, footnotesDivBytes)
|
||||
r.outHRTag(w)
|
||||
r.cr(w)
|
||||
}
|
||||
r.cr(w)
|
||||
if node.Parent.Type == Item && node.Parent.Parent.Tight {
|
||||
r.cr(w)
|
||||
}
|
||||
r.tag(w, openTag[:len(openTag)-1], attrs)
|
||||
r.cr(w)
|
||||
} else {
|
||||
r.out(w, closeTag)
|
||||
//cr(w)
|
||||
//if node.parent.Type != Item {
|
||||
// cr(w)
|
||||
//}
|
||||
if node.Parent.Type == Item && node.Next != nil {
|
||||
r.cr(w)
|
||||
}
|
||||
if node.Parent.Type == Document || node.Parent.Type == BlockQuote {
|
||||
r.cr(w)
|
||||
}
|
||||
if node.IsFootnotesList {
|
||||
r.out(w, footnotesCloseDivBytes)
|
||||
}
|
||||
}
|
||||
case Item:
|
||||
openTag := liTag
|
||||
closeTag := liCloseTag
|
||||
if node.ListFlags&ListTypeDefinition != 0 {
|
||||
openTag = ddTag
|
||||
closeTag = ddCloseTag
|
||||
}
|
||||
if node.ListFlags&ListTypeTerm != 0 {
|
||||
openTag = dtTag
|
||||
closeTag = dtCloseTag
|
||||
}
|
||||
if entering {
|
||||
if itemOpenCR(node) {
|
||||
r.cr(w)
|
||||
}
|
||||
if node.ListData.RefLink != nil {
|
||||
slug := slugify(node.ListData.RefLink)
|
||||
r.out(w, footnoteItem(r.FootnoteAnchorPrefix, slug))
|
||||
break
|
||||
}
|
||||
r.out(w, openTag)
|
||||
} else {
|
||||
if node.ListData.RefLink != nil {
|
||||
slug := slugify(node.ListData.RefLink)
|
||||
if r.Flags&FootnoteReturnLinks != 0 {
|
||||
r.out(w, footnoteReturnLink(r.FootnoteAnchorPrefix, r.FootnoteReturnLinkContents, slug))
|
||||
}
|
||||
}
|
||||
r.out(w, closeTag)
|
||||
r.cr(w)
|
||||
}
|
||||
case CodeBlock:
|
||||
attrs = appendLanguageAttr(attrs, node.Info)
|
||||
r.cr(w)
|
||||
r.out(w, preTag)
|
||||
r.tag(w, codeTag[:len(codeTag)-1], attrs)
|
||||
escapeAllHTML(w, node.Literal)
|
||||
r.out(w, codeCloseTag)
|
||||
r.out(w, preCloseTag)
|
||||
if node.Parent.Type != Item {
|
||||
r.cr(w)
|
||||
}
|
||||
case Table:
|
||||
if entering {
|
||||
r.cr(w)
|
||||
r.out(w, tableTag)
|
||||
} else {
|
||||
r.out(w, tableCloseTag)
|
||||
r.cr(w)
|
||||
}
|
||||
case TableCell:
|
||||
openTag := tdTag
|
||||
closeTag := tdCloseTag
|
||||
if node.IsHeader {
|
||||
openTag = thTag
|
||||
closeTag = thCloseTag
|
||||
}
|
||||
if entering {
|
||||
align := cellAlignment(node.Align)
|
||||
if align != "" {
|
||||
attrs = append(attrs, fmt.Sprintf(`align="%s"`, align))
|
||||
}
|
||||
if node.Prev == nil {
|
||||
r.cr(w)
|
||||
}
|
||||
r.tag(w, openTag, attrs)
|
||||
} else {
|
||||
r.out(w, closeTag)
|
||||
r.cr(w)
|
||||
}
|
||||
case TableHead:
|
||||
if entering {
|
||||
r.cr(w)
|
||||
r.out(w, theadTag)
|
||||
} else {
|
||||
r.out(w, theadCloseTag)
|
||||
r.cr(w)
|
||||
}
|
||||
case TableBody:
|
||||
if entering {
|
||||
r.cr(w)
|
||||
r.out(w, tbodyTag)
|
||||
// XXX: this is to adhere to a rather silly test. Should fix test.
|
||||
if node.FirstChild == nil {
|
||||
r.cr(w)
|
||||
}
|
||||
} else {
|
||||
r.out(w, tbodyCloseTag)
|
||||
r.cr(w)
|
||||
}
|
||||
case TableRow:
|
||||
if entering {
|
||||
r.cr(w)
|
||||
r.out(w, trTag)
|
||||
} else {
|
||||
r.out(w, trCloseTag)
|
||||
r.cr(w)
|
||||
}
|
||||
default:
|
||||
panic("Unknown node type " + node.Type.String())
|
||||
}
|
||||
return GoToNext
|
||||
}
|
||||
|
||||
// RenderHeader writes HTML document preamble and TOC if requested.
|
||||
func (r *HTMLRenderer) RenderHeader(w io.Writer, ast *Node) {
|
||||
r.writeDocumentHeader(w)
|
||||
if r.Flags&TOC != 0 {
|
||||
r.writeTOC(w, ast)
|
||||
}
|
||||
}
|
||||
|
||||
// RenderFooter writes HTML document footer.
|
||||
func (r *HTMLRenderer) RenderFooter(w io.Writer, ast *Node) {
|
||||
if r.Flags&CompletePage == 0 {
|
||||
return
|
||||
}
|
||||
io.WriteString(w, "\n</body>\n</html>\n")
|
||||
}
|
||||
|
||||
func (r *HTMLRenderer) writeDocumentHeader(w io.Writer) {
|
||||
if r.Flags&CompletePage == 0 {
|
||||
return
|
||||
}
|
||||
ending := ""
|
||||
if r.Flags&UseXHTML != 0 {
|
||||
io.WriteString(w, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
|
||||
io.WriteString(w, "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
|
||||
io.WriteString(w, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
|
||||
ending = " /"
|
||||
} else {
|
||||
io.WriteString(w, "<!DOCTYPE html>\n")
|
||||
io.WriteString(w, "<html>\n")
|
||||
}
|
||||
io.WriteString(w, "<head>\n")
|
||||
io.WriteString(w, " <title>")
|
||||
if r.Flags&Smartypants != 0 {
|
||||
r.sr.Process(w, []byte(r.Title))
|
||||
} else {
|
||||
escapeHTML(w, []byte(r.Title))
|
||||
}
|
||||
io.WriteString(w, "</title>\n")
|
||||
io.WriteString(w, " <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
|
||||
io.WriteString(w, Version)
|
||||
io.WriteString(w, "\"")
|
||||
io.WriteString(w, ending)
|
||||
io.WriteString(w, ">\n")
|
||||
io.WriteString(w, " <meta charset=\"utf-8\"")
|
||||
io.WriteString(w, ending)
|
||||
io.WriteString(w, ">\n")
|
||||
if r.CSS != "" {
|
||||
io.WriteString(w, " <link rel=\"stylesheet\" type=\"text/css\" href=\"")
|
||||
escapeHTML(w, []byte(r.CSS))
|
||||
io.WriteString(w, "\"")
|
||||
io.WriteString(w, ending)
|
||||
io.WriteString(w, ">\n")
|
||||
}
|
||||
if r.Icon != "" {
|
||||
io.WriteString(w, " <link rel=\"icon\" type=\"image/x-icon\" href=\"")
|
||||
escapeHTML(w, []byte(r.Icon))
|
||||
io.WriteString(w, "\"")
|
||||
io.WriteString(w, ending)
|
||||
io.WriteString(w, ">\n")
|
||||
}
|
||||
io.WriteString(w, "</head>\n")
|
||||
io.WriteString(w, "<body>\n\n")
|
||||
}
|
||||
|
||||
func (r *HTMLRenderer) writeTOC(w io.Writer, ast *Node) {
|
||||
buf := bytes.Buffer{}
|
||||
|
||||
inHeading := false
|
||||
tocLevel := 0
|
||||
headingCount := 0
|
||||
|
||||
ast.Walk(func(node *Node, entering bool) WalkStatus {
|
||||
if node.Type == Heading && !node.HeadingData.IsTitleblock {
|
||||
inHeading = entering
|
||||
if entering {
|
||||
node.HeadingID = fmt.Sprintf("toc_%d", headingCount)
|
||||
if node.Level == tocLevel {
|
||||
buf.WriteString("</li>\n\n<li>")
|
||||
} else if node.Level < tocLevel {
|
||||
for node.Level < tocLevel {
|
||||
tocLevel--
|
||||
buf.WriteString("</li>\n</ul>")
|
||||
}
|
||||
buf.WriteString("</li>\n\n<li>")
|
||||
} else {
|
||||
for node.Level > tocLevel {
|
||||
tocLevel++
|
||||
buf.WriteString("\n<ul>\n<li>")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(&buf, `<a href="#toc_%d">`, headingCount)
|
||||
headingCount++
|
||||
} else {
|
||||
buf.WriteString("</a>")
|
||||
}
|
||||
return GoToNext
|
||||
}
|
||||
|
||||
if inHeading {
|
||||
return r.RenderNode(&buf, node, entering)
|
||||
}
|
||||
|
||||
return GoToNext
|
||||
})
|
||||
|
||||
for ; tocLevel > 0; tocLevel-- {
|
||||
buf.WriteString("</li>\n</ul>")
|
||||
}
|
||||
|
||||
if buf.Len() > 0 {
|
||||
io.WriteString(w, "<nav>\n")
|
||||
w.Write(buf.Bytes())
|
||||
io.WriteString(w, "\n\n</nav>\n")
|
||||
}
|
||||
r.lastOutputLen = buf.Len()
|
||||
}
|
||||
-1228
File diff suppressed because it is too large
Load Diff
-950
@@ -1,950 +0,0 @@
|
||||
// Blackfriday Markdown Processor
|
||||
// Available at http://github.com/russross/blackfriday
|
||||
//
|
||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||
// Distributed under the Simplified BSD License.
|
||||
// See README.md for details.
|
||||
|
||||
package blackfriday
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
//
|
||||
// Markdown parsing and processing
|
||||
//
|
||||
|
||||
// Version string of the package. Appears in the rendered document when
|
||||
// CompletePage flag is on.
|
||||
const Version = "2.0"
|
||||
|
||||
// Extensions is a bitwise or'ed collection of enabled Blackfriday's
|
||||
// extensions.
|
||||
type Extensions int
|
||||
|
||||
// These are the supported markdown parsing extensions.
|
||||
// OR these values together to select multiple extensions.
|
||||
const (
|
||||
NoExtensions Extensions = 0
|
||||
NoIntraEmphasis Extensions = 1 << iota // Ignore emphasis markers inside words
|
||||
Tables // Render tables
|
||||
FencedCode // Render fenced code blocks
|
||||
Autolink // Detect embedded URLs that are not explicitly marked
|
||||
Strikethrough // Strikethrough text using ~~test~~
|
||||
LaxHTMLBlocks // Loosen up HTML block parsing rules
|
||||
SpaceHeadings // Be strict about prefix heading rules
|
||||
HardLineBreak // Translate newlines into line breaks
|
||||
TabSizeEight // Expand tabs to eight spaces instead of four
|
||||
Footnotes // Pandoc-style footnotes
|
||||
NoEmptyLineBeforeBlock // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
|
||||
HeadingIDs // specify heading IDs with {#id}
|
||||
Titleblock // Titleblock ala pandoc
|
||||
AutoHeadingIDs // Create the heading ID from the text
|
||||
BackslashLineBreak // Translate trailing backslashes into line breaks
|
||||
DefinitionLists // Render definition lists
|
||||
|
||||
CommonHTMLFlags HTMLFlags = UseXHTML | Smartypants |
|
||||
SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes
|
||||
|
||||
CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode |
|
||||
Autolink | Strikethrough | SpaceHeadings | HeadingIDs |
|
||||
BackslashLineBreak | DefinitionLists
|
||||
)
|
||||
|
||||
// ListType contains bitwise or'ed flags for list and list item objects.
|
||||
type ListType int
|
||||
|
||||
// These are the possible flag values for the ListItem renderer.
|
||||
// Multiple flag values may be ORed together.
|
||||
// These are mostly of interest if you are writing a new output format.
|
||||
const (
|
||||
ListTypeOrdered ListType = 1 << iota
|
||||
ListTypeDefinition
|
||||
ListTypeTerm
|
||||
|
||||
ListItemContainsBlock
|
||||
ListItemBeginningOfList // TODO: figure out if this is of any use now
|
||||
ListItemEndOfList
|
||||
)
|
||||
|
||||
// CellAlignFlags holds a type of alignment in a table cell.
|
||||
type CellAlignFlags int
|
||||
|
||||
// These are the possible flag values for the table cell renderer.
|
||||
// Only a single one of these values will be used; they are not ORed together.
|
||||
// These are mostly of interest if you are writing a new output format.
|
||||
const (
|
||||
TableAlignmentLeft CellAlignFlags = 1 << iota
|
||||
TableAlignmentRight
|
||||
TableAlignmentCenter = (TableAlignmentLeft | TableAlignmentRight)
|
||||
)
|
||||
|
||||
// The size of a tab stop.
|
||||
const (
|
||||
TabSizeDefault = 4
|
||||
TabSizeDouble = 8
|
||||
)
|
||||
|
||||
// blockTags is a set of tags that are recognized as HTML block tags.
|
||||
// Any of these can be included in markdown text without special escaping.
|
||||
var blockTags = map[string]struct{}{
|
||||
"blockquote": {},
|
||||
"del": {},
|
||||
"div": {},
|
||||
"dl": {},
|
||||
"fieldset": {},
|
||||
"form": {},
|
||||
"h1": {},
|
||||
"h2": {},
|
||||
"h3": {},
|
||||
"h4": {},
|
||||
"h5": {},
|
||||
"h6": {},
|
||||
"iframe": {},
|
||||
"ins": {},
|
||||
"math": {},
|
||||
"noscript": {},
|
||||
"ol": {},
|
||||
"pre": {},
|
||||
"p": {},
|
||||
"script": {},
|
||||
"style": {},
|
||||
"table": {},
|
||||
"ul": {},
|
||||
|
||||
// HTML5
|
||||
"address": {},
|
||||
"article": {},
|
||||
"aside": {},
|
||||
"canvas": {},
|
||||
"figcaption": {},
|
||||
"figure": {},
|
||||
"footer": {},
|
||||
"header": {},
|
||||
"hgroup": {},
|
||||
"main": {},
|
||||
"nav": {},
|
||||
"output": {},
|
||||
"progress": {},
|
||||
"section": {},
|
||||
"video": {},
|
||||
}
|
||||
|
||||
// Renderer is the rendering interface. This is mostly of interest if you are
|
||||
// implementing a new rendering format.
|
||||
//
|
||||
// Only an HTML implementation is provided in this repository, see the README
|
||||
// for external implementations.
|
||||
type Renderer interface {
|
||||
// RenderNode is the main rendering method. It will be called once for
|
||||
// every leaf node and twice for every non-leaf node (first with
|
||||
// entering=true, then with entering=false). The method should write its
|
||||
// rendition of the node to the supplied writer w.
|
||||
RenderNode(w io.Writer, node *Node, entering bool) WalkStatus
|
||||
|
||||
// RenderHeader is a method that allows the renderer to produce some
|
||||
// content preceding the main body of the output document. The header is
|
||||
// understood in the broad sense here. For example, the default HTML
|
||||
// renderer will write not only the HTML document preamble, but also the
|
||||
// table of contents if it was requested.
|
||||
//
|
||||
// The method will be passed an entire document tree, in case a particular
|
||||
// implementation needs to inspect it to produce output.
|
||||
//
|
||||
// The output should be written to the supplied writer w. If your
|
||||
// implementation has no header to write, supply an empty implementation.
|
||||
RenderHeader(w io.Writer, ast *Node)
|
||||
|
||||
// RenderFooter is a symmetric counterpart of RenderHeader.
|
||||
RenderFooter(w io.Writer, ast *Node)
|
||||
}
|
||||
|
||||
// Callback functions for inline parsing. One such function is defined
|
||||
// for each character that triggers a response when parsing inline data.
|
||||
type inlineParser func(p *Markdown, data []byte, offset int) (int, *Node)
|
||||
|
||||
// Markdown is a type that holds extensions and the runtime state used by
|
||||
// Parse, and the renderer. You can not use it directly, construct it with New.
|
||||
type Markdown struct {
|
||||
renderer Renderer
|
||||
referenceOverride ReferenceOverrideFunc
|
||||
refs map[string]*reference
|
||||
inlineCallback [256]inlineParser
|
||||
extensions Extensions
|
||||
nesting int
|
||||
maxNesting int
|
||||
insideLink bool
|
||||
|
||||
// Footnotes need to be ordered as well as available to quickly check for
|
||||
// presence. If a ref is also a footnote, it's stored both in refs and here
|
||||
// in notes. Slice is nil if footnotes not enabled.
|
||||
notes []*reference
|
||||
|
||||
doc *Node
|
||||
tip *Node // = doc
|
||||
oldTip *Node
|
||||
lastMatchedContainer *Node // = doc
|
||||
allClosed bool
|
||||
}
|
||||
|
||||
func (p *Markdown) getRef(refid string) (ref *reference, found bool) {
|
||||
if p.referenceOverride != nil {
|
||||
r, overridden := p.referenceOverride(refid)
|
||||
if overridden {
|
||||
if r == nil {
|
||||
return nil, false
|
||||
}
|
||||
return &reference{
|
||||
link: []byte(r.Link),
|
||||
title: []byte(r.Title),
|
||||
noteID: 0,
|
||||
hasBlock: false,
|
||||
text: []byte(r.Text)}, true
|
||||
}
|
||||
}
|
||||
// refs are case insensitive
|
||||
ref, found = p.refs[strings.ToLower(refid)]
|
||||
return ref, found
|
||||
}
|
||||
|
||||
func (p *Markdown) finalize(block *Node) {
|
||||
above := block.Parent
|
||||
block.open = false
|
||||
p.tip = above
|
||||
}
|
||||
|
||||
func (p *Markdown) addChild(node NodeType, offset uint32) *Node {
|
||||
return p.addExistingChild(NewNode(node), offset)
|
||||
}
|
||||
|
||||
func (p *Markdown) addExistingChild(node *Node, offset uint32) *Node {
|
||||
for !p.tip.canContain(node.Type) {
|
||||
p.finalize(p.tip)
|
||||
}
|
||||
p.tip.AppendChild(node)
|
||||
p.tip = node
|
||||
return node
|
||||
}
|
||||
|
||||
func (p *Markdown) closeUnmatchedBlocks() {
|
||||
if !p.allClosed {
|
||||
for p.oldTip != p.lastMatchedContainer {
|
||||
parent := p.oldTip.Parent
|
||||
p.finalize(p.oldTip)
|
||||
p.oldTip = parent
|
||||
}
|
||||
p.allClosed = true
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Public interface
|
||||
//
|
||||
//
|
||||
|
||||
// Reference represents the details of a link.
|
||||
// See the documentation in Options for more details on use-case.
|
||||
type Reference struct {
|
||||
// Link is usually the URL the reference points to.
|
||||
Link string
|
||||
// Title is the alternate text describing the link in more detail.
|
||||
Title string
|
||||
// Text is the optional text to override the ref with if the syntax used was
|
||||
// [refid][]
|
||||
Text string
|
||||
}
|
||||
|
||||
// ReferenceOverrideFunc is expected to be called with a reference string and
|
||||
// return either a valid Reference type that the reference string maps to or
|
||||
// nil. If overridden is false, the default reference logic will be executed.
|
||||
// See the documentation in Options for more details on use-case.
|
||||
type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool)
|
||||
|
||||
// New constructs a Markdown processor. You can use the same With* functions as
|
||||
// for Run() to customize parser's behavior and the renderer.
|
||||
func New(opts ...Option) *Markdown {
|
||||
var p Markdown
|
||||
for _, opt := range opts {
|
||||
opt(&p)
|
||||
}
|
||||
p.refs = make(map[string]*reference)
|
||||
p.maxNesting = 16
|
||||
p.insideLink = false
|
||||
docNode := NewNode(Document)
|
||||
p.doc = docNode
|
||||
p.tip = docNode
|
||||
p.oldTip = docNode
|
||||
p.lastMatchedContainer = docNode
|
||||
p.allClosed = true
|
||||
// register inline parsers
|
||||
p.inlineCallback[' '] = maybeLineBreak
|
||||
p.inlineCallback['*'] = emphasis
|
||||
p.inlineCallback['_'] = emphasis
|
||||
if p.extensions&Strikethrough != 0 {
|
||||
p.inlineCallback['~'] = emphasis
|
||||
}
|
||||
p.inlineCallback['`'] = codeSpan
|
||||
p.inlineCallback['\n'] = lineBreak
|
||||
p.inlineCallback['['] = link
|
||||
p.inlineCallback['<'] = leftAngle
|
||||
p.inlineCallback['\\'] = escape
|
||||
p.inlineCallback['&'] = entity
|
||||
p.inlineCallback['!'] = maybeImage
|
||||
p.inlineCallback['^'] = maybeInlineFootnote
|
||||
if p.extensions&Autolink != 0 {
|
||||
p.inlineCallback['h'] = maybeAutoLink
|
||||
p.inlineCallback['m'] = maybeAutoLink
|
||||
p.inlineCallback['f'] = maybeAutoLink
|
||||
p.inlineCallback['H'] = maybeAutoLink
|
||||
p.inlineCallback['M'] = maybeAutoLink
|
||||
p.inlineCallback['F'] = maybeAutoLink
|
||||
}
|
||||
if p.extensions&Footnotes != 0 {
|
||||
p.notes = make([]*reference, 0)
|
||||
}
|
||||
return &p
|
||||
}
|
||||
|
||||
// Option customizes the Markdown processor's default behavior.
|
||||
type Option func(*Markdown)
|
||||
|
||||
// WithRenderer allows you to override the default renderer.
|
||||
func WithRenderer(r Renderer) Option {
|
||||
return func(p *Markdown) {
|
||||
p.renderer = r
|
||||
}
|
||||
}
|
||||
|
||||
// WithExtensions allows you to pick some of the many extensions provided by
|
||||
// Blackfriday. You can bitwise OR them.
|
||||
func WithExtensions(e Extensions) Option {
|
||||
return func(p *Markdown) {
|
||||
p.extensions = e
|
||||
}
|
||||
}
|
||||
|
||||
// WithNoExtensions turns off all extensions and custom behavior.
|
||||
func WithNoExtensions() Option {
|
||||
return func(p *Markdown) {
|
||||
p.extensions = NoExtensions
|
||||
p.renderer = NewHTMLRenderer(HTMLRendererParameters{
|
||||
Flags: HTMLFlagsNone,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithRefOverride sets an optional function callback that is called every
|
||||
// time a reference is resolved.
|
||||
//
|
||||
// In Markdown, the link reference syntax can be made to resolve a link to
|
||||
// a reference instead of an inline URL, in one of the following ways:
|
||||
//
|
||||
// * [link text][refid]
|
||||
// * [refid][]
|
||||
//
|
||||
// Usually, the refid is defined at the bottom of the Markdown document. If
|
||||
// this override function is provided, the refid is passed to the override
|
||||
// function first, before consulting the defined refids at the bottom. If
|
||||
// the override function indicates an override did not occur, the refids at
|
||||
// the bottom will be used to fill in the link details.
|
||||
func WithRefOverride(o ReferenceOverrideFunc) Option {
|
||||
return func(p *Markdown) {
|
||||
p.referenceOverride = o
|
||||
}
|
||||
}
|
||||
|
||||
// Run is the main entry point to Blackfriday. It parses and renders a
|
||||
// block of markdown-encoded text.
|
||||
//
|
||||
// The simplest invocation of Run takes one argument, input:
|
||||
// output := Run(input)
|
||||
// This will parse the input with CommonExtensions enabled and render it with
|
||||
// the default HTMLRenderer (with CommonHTMLFlags).
|
||||
//
|
||||
// Variadic arguments opts can customize the default behavior. Since Markdown
|
||||
// type does not contain exported fields, you can not use it directly. Instead,
|
||||
// use the With* functions. For example, this will call the most basic
|
||||
// functionality, with no extensions:
|
||||
// output := Run(input, WithNoExtensions())
|
||||
//
|
||||
// You can use any number of With* arguments, even contradicting ones. They
|
||||
// will be applied in order of appearance and the latter will override the
|
||||
// former:
|
||||
// output := Run(input, WithNoExtensions(), WithExtensions(exts),
|
||||
// WithRenderer(yourRenderer))
|
||||
func Run(input []byte, opts ...Option) []byte {
|
||||
r := NewHTMLRenderer(HTMLRendererParameters{
|
||||
Flags: CommonHTMLFlags,
|
||||
})
|
||||
optList := []Option{WithRenderer(r), WithExtensions(CommonExtensions)}
|
||||
optList = append(optList, opts...)
|
||||
parser := New(optList...)
|
||||
ast := parser.Parse(input)
|
||||
var buf bytes.Buffer
|
||||
parser.renderer.RenderHeader(&buf, ast)
|
||||
ast.Walk(func(node *Node, entering bool) WalkStatus {
|
||||
return parser.renderer.RenderNode(&buf, node, entering)
|
||||
})
|
||||
parser.renderer.RenderFooter(&buf, ast)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// Parse is an entry point to the parsing part of Blackfriday. It takes an
|
||||
// input markdown document and produces a syntax tree for its contents. This
|
||||
// tree can then be rendered with a default or custom renderer, or
|
||||
// analyzed/transformed by the caller to whatever non-standard needs they have.
|
||||
// The return value is the root node of the syntax tree.
|
||||
func (p *Markdown) Parse(input []byte) *Node {
|
||||
p.block(input)
|
||||
// Walk the tree and finish up some of unfinished blocks
|
||||
for p.tip != nil {
|
||||
p.finalize(p.tip)
|
||||
}
|
||||
// Walk the tree again and process inline markdown in each block
|
||||
p.doc.Walk(func(node *Node, entering bool) WalkStatus {
|
||||
if node.Type == Paragraph || node.Type == Heading || node.Type == TableCell {
|
||||
p.inline(node, node.content)
|
||||
node.content = nil
|
||||
}
|
||||
return GoToNext
|
||||
})
|
||||
p.parseRefsToAST()
|
||||
return p.doc
|
||||
}
|
||||
|
||||
func (p *Markdown) parseRefsToAST() {
|
||||
if p.extensions&Footnotes == 0 || len(p.notes) == 0 {
|
||||
return
|
||||
}
|
||||
p.tip = p.doc
|
||||
block := p.addBlock(List, nil)
|
||||
block.IsFootnotesList = true
|
||||
block.ListFlags = ListTypeOrdered
|
||||
flags := ListItemBeginningOfList
|
||||
// Note: this loop is intentionally explicit, not range-form. This is
|
||||
// because the body of the loop will append nested footnotes to p.notes and
|
||||
// we need to process those late additions. Range form would only walk over
|
||||
// the fixed initial set.
|
||||
for i := 0; i < len(p.notes); i++ {
|
||||
ref := p.notes[i]
|
||||
p.addExistingChild(ref.footnote, 0)
|
||||
block := ref.footnote
|
||||
block.ListFlags = flags | ListTypeOrdered
|
||||
block.RefLink = ref.link
|
||||
if ref.hasBlock {
|
||||
flags |= ListItemContainsBlock
|
||||
p.block(ref.title)
|
||||
} else {
|
||||
p.inline(block, ref.title)
|
||||
}
|
||||
flags &^= ListItemBeginningOfList | ListItemContainsBlock
|
||||
}
|
||||
above := block.Parent
|
||||
finalizeList(block)
|
||||
p.tip = above
|
||||
block.Walk(func(node *Node, entering bool) WalkStatus {
|
||||
if node.Type == Paragraph || node.Type == Heading {
|
||||
p.inline(node, node.content)
|
||||
node.content = nil
|
||||
}
|
||||
return GoToNext
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// Link references
|
||||
//
|
||||
// This section implements support for references that (usually) appear
|
||||
// as footnotes in a document, and can be referenced anywhere in the document.
|
||||
// The basic format is:
|
||||
//
|
||||
// [1]: http://www.google.com/ "Google"
|
||||
// [2]: http://www.github.com/ "Github"
|
||||
//
|
||||
// Anywhere in the document, the reference can be linked by referring to its
|
||||
// label, i.e., 1 and 2 in this example, as in:
|
||||
//
|
||||
// This library is hosted on [Github][2], a git hosting site.
|
||||
//
|
||||
// Actual footnotes as specified in Pandoc and supported by some other Markdown
|
||||
// libraries such as php-markdown are also taken care of. They look like this:
|
||||
//
|
||||
// This sentence needs a bit of further explanation.[^note]
|
||||
//
|
||||
// [^note]: This is the explanation.
|
||||
//
|
||||
// Footnotes should be placed at the end of the document in an ordered list.
|
||||
// Finally, there are inline footnotes such as:
|
||||
//
|
||||
// Inline footnotes^[Also supported.] provide a quick inline explanation,
|
||||
// but are rendered at the bottom of the document.
|
||||
//
|
||||
|
||||
// reference holds all information necessary for a reference-style links or
|
||||
// footnotes.
|
||||
//
|
||||
// Consider this markdown with reference-style links:
|
||||
//
|
||||
// [link][ref]
|
||||
//
|
||||
// [ref]: /url/ "tooltip title"
|
||||
//
|
||||
// It will be ultimately converted to this HTML:
|
||||
//
|
||||
// <p><a href=\"/url/\" title=\"title\">link</a></p>
|
||||
//
|
||||
// And a reference structure will be populated as follows:
|
||||
//
|
||||
// p.refs["ref"] = &reference{
|
||||
// link: "/url/",
|
||||
// title: "tooltip title",
|
||||
// }
|
||||
//
|
||||
// Alternatively, reference can contain information about a footnote. Consider
|
||||
// this markdown:
|
||||
//
|
||||
// Text needing a footnote.[^a]
|
||||
//
|
||||
// [^a]: This is the note
|
||||
//
|
||||
// A reference structure will be populated as follows:
|
||||
//
|
||||
// p.refs["a"] = &reference{
|
||||
// link: "a",
|
||||
// title: "This is the note",
|
||||
// noteID: <some positive int>,
|
||||
// }
|
||||
//
|
||||
// TODO: As you can see, it begs for splitting into two dedicated structures
|
||||
// for refs and for footnotes.
|
||||
type reference struct {
|
||||
link []byte
|
||||
title []byte
|
||||
noteID int // 0 if not a footnote ref
|
||||
hasBlock bool
|
||||
footnote *Node // a link to the Item node within a list of footnotes
|
||||
|
||||
text []byte // only gets populated by refOverride feature with Reference.Text
|
||||
}
|
||||
|
||||
func (r *reference) String() string {
|
||||
return fmt.Sprintf("{link: %q, title: %q, text: %q, noteID: %d, hasBlock: %v}",
|
||||
r.link, r.title, r.text, r.noteID, r.hasBlock)
|
||||
}
|
||||
|
||||
// Check whether or not data starts with a reference link.
|
||||
// If so, it is parsed and stored in the list of references
|
||||
// (in the render struct).
|
||||
// Returns the number of bytes to skip to move past it,
|
||||
// or zero if the first line is not a reference.
|
||||
func isReference(p *Markdown, data []byte, tabSize int) int {
|
||||
// up to 3 optional leading spaces
|
||||
if len(data) < 4 {
|
||||
return 0
|
||||
}
|
||||
i := 0
|
||||
for i < 3 && data[i] == ' ' {
|
||||
i++
|
||||
}
|
||||
|
||||
noteID := 0
|
||||
|
||||
// id part: anything but a newline between brackets
|
||||
if data[i] != '[' {
|
||||
return 0
|
||||
}
|
||||
i++
|
||||
if p.extensions&Footnotes != 0 {
|
||||
if i < len(data) && data[i] == '^' {
|
||||
// we can set it to anything here because the proper noteIds will
|
||||
// be assigned later during the second pass. It just has to be != 0
|
||||
noteID = 1
|
||||
i++
|
||||
}
|
||||
}
|
||||
idOffset := i
|
||||
for i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != ']' {
|
||||
i++
|
||||
}
|
||||
if i >= len(data) || data[i] != ']' {
|
||||
return 0
|
||||
}
|
||||
idEnd := i
|
||||
// footnotes can have empty ID, like this: [^], but a reference can not be
|
||||
// empty like this: []. Break early if it's not a footnote and there's no ID
|
||||
if noteID == 0 && idOffset == idEnd {
|
||||
return 0
|
||||
}
|
||||
// spacer: colon (space | tab)* newline? (space | tab)*
|
||||
i++
|
||||
if i >= len(data) || data[i] != ':' {
|
||||
return 0
|
||||
}
|
||||
i++
|
||||
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
|
||||
i++
|
||||
}
|
||||
if i < len(data) && (data[i] == '\n' || data[i] == '\r') {
|
||||
i++
|
||||
if i < len(data) && data[i] == '\n' && data[i-1] == '\r' {
|
||||
i++
|
||||
}
|
||||
}
|
||||
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
|
||||
i++
|
||||
}
|
||||
if i >= len(data) {
|
||||
return 0
|
||||
}
|
||||
|
||||
var (
|
||||
linkOffset, linkEnd int
|
||||
titleOffset, titleEnd int
|
||||
lineEnd int
|
||||
raw []byte
|
||||
hasBlock bool
|
||||
)
|
||||
|
||||
if p.extensions&Footnotes != 0 && noteID != 0 {
|
||||
linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize)
|
||||
lineEnd = linkEnd
|
||||
} else {
|
||||
linkOffset, linkEnd, titleOffset, titleEnd, lineEnd = scanLinkRef(p, data, i)
|
||||
}
|
||||
if lineEnd == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// a valid ref has been found
|
||||
|
||||
ref := &reference{
|
||||
noteID: noteID,
|
||||
hasBlock: hasBlock,
|
||||
}
|
||||
|
||||
if noteID > 0 {
|
||||
// reusing the link field for the id since footnotes don't have links
|
||||
ref.link = data[idOffset:idEnd]
|
||||
// if footnote, it's not really a title, it's the contained text
|
||||
ref.title = raw
|
||||
} else {
|
||||
ref.link = data[linkOffset:linkEnd]
|
||||
ref.title = data[titleOffset:titleEnd]
|
||||
}
|
||||
|
||||
// id matches are case-insensitive
|
||||
id := string(bytes.ToLower(data[idOffset:idEnd]))
|
||||
|
||||
p.refs[id] = ref
|
||||
|
||||
return lineEnd
|
||||
}
|
||||
|
||||
func scanLinkRef(p *Markdown, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) {
|
||||
// link: whitespace-free sequence, optionally between angle brackets
|
||||
if data[i] == '<' {
|
||||
i++
|
||||
}
|
||||
linkOffset = i
|
||||
for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
|
||||
i++
|
||||
}
|
||||
linkEnd = i
|
||||
if data[linkOffset] == '<' && data[linkEnd-1] == '>' {
|
||||
linkOffset++
|
||||
linkEnd--
|
||||
}
|
||||
|
||||
// optional spacer: (space | tab)* (newline | '\'' | '"' | '(' )
|
||||
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
|
||||
i++
|
||||
}
|
||||
if i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != '\'' && data[i] != '"' && data[i] != '(' {
|
||||
return
|
||||
}
|
||||
|
||||
// compute end-of-line
|
||||
if i >= len(data) || data[i] == '\r' || data[i] == '\n' {
|
||||
lineEnd = i
|
||||
}
|
||||
if i+1 < len(data) && data[i] == '\r' && data[i+1] == '\n' {
|
||||
lineEnd++
|
||||
}
|
||||
|
||||
// optional (space|tab)* spacer after a newline
|
||||
if lineEnd > 0 {
|
||||
i = lineEnd + 1
|
||||
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// optional title: any non-newline sequence enclosed in '"() alone on its line
|
||||
if i+1 < len(data) && (data[i] == '\'' || data[i] == '"' || data[i] == '(') {
|
||||
i++
|
||||
titleOffset = i
|
||||
|
||||
// look for EOL
|
||||
for i < len(data) && data[i] != '\n' && data[i] != '\r' {
|
||||
i++
|
||||
}
|
||||
if i+1 < len(data) && data[i] == '\n' && data[i+1] == '\r' {
|
||||
titleEnd = i + 1
|
||||
} else {
|
||||
titleEnd = i
|
||||
}
|
||||
|
||||
// step back
|
||||
i--
|
||||
for i > titleOffset && (data[i] == ' ' || data[i] == '\t') {
|
||||
i--
|
||||
}
|
||||
if i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')') {
|
||||
lineEnd = titleEnd
|
||||
titleEnd = i
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// The first bit of this logic is the same as Parser.listItem, but the rest
|
||||
// is much simpler. This function simply finds the entire block and shifts it
|
||||
// over by one tab if it is indeed a block (just returns the line if it's not).
|
||||
// blockEnd is the end of the section in the input buffer, and contents is the
|
||||
// extracted text that was shifted over one tab. It will need to be rendered at
|
||||
// the end of the document.
|
||||
func scanFootnote(p *Markdown, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) {
|
||||
if i == 0 || len(data) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// skip leading whitespace on first line
|
||||
for i < len(data) && data[i] == ' ' {
|
||||
i++
|
||||
}
|
||||
|
||||
blockStart = i
|
||||
|
||||
// find the end of the line
|
||||
blockEnd = i
|
||||
for i < len(data) && data[i-1] != '\n' {
|
||||
i++
|
||||
}
|
||||
|
||||
// get working buffer
|
||||
var raw bytes.Buffer
|
||||
|
||||
// put the first line into the working buffer
|
||||
raw.Write(data[blockEnd:i])
|
||||
blockEnd = i
|
||||
|
||||
// process the following lines
|
||||
containsBlankLine := false
|
||||
|
||||
gatherLines:
|
||||
for blockEnd < len(data) {
|
||||
i++
|
||||
|
||||
// find the end of this line
|
||||
for i < len(data) && data[i-1] != '\n' {
|
||||
i++
|
||||
}
|
||||
|
||||
// if it is an empty line, guess that it is part of this item
|
||||
// and move on to the next line
|
||||
if p.isEmpty(data[blockEnd:i]) > 0 {
|
||||
containsBlankLine = true
|
||||
blockEnd = i
|
||||
continue
|
||||
}
|
||||
|
||||
n := 0
|
||||
if n = isIndented(data[blockEnd:i], indentSize); n == 0 {
|
||||
// this is the end of the block.
|
||||
// we don't want to include this last line in the index.
|
||||
break gatherLines
|
||||
}
|
||||
|
||||
// if there were blank lines before this one, insert a new one now
|
||||
if containsBlankLine {
|
||||
raw.WriteByte('\n')
|
||||
containsBlankLine = false
|
||||
}
|
||||
|
||||
// get rid of that first tab, write to buffer
|
||||
raw.Write(data[blockEnd+n : i])
|
||||
hasBlock = true
|
||||
|
||||
blockEnd = i
|
||||
}
|
||||
|
||||
if data[blockEnd-1] != '\n' {
|
||||
raw.WriteByte('\n')
|
||||
}
|
||||
|
||||
contents = raw.Bytes()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Miscellaneous helper functions
|
||||
//
|
||||
//
|
||||
|
||||
// Test if a character is a punctuation symbol.
|
||||
// Taken from a private function in regexp in the stdlib.
|
||||
func ispunct(c byte) bool {
|
||||
for _, r := range []byte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") {
|
||||
if c == r {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Test if a character is a whitespace character.
|
||||
func isspace(c byte) bool {
|
||||
return ishorizontalspace(c) || isverticalspace(c)
|
||||
}
|
||||
|
||||
// Test if a character is a horizontal whitespace character.
|
||||
func ishorizontalspace(c byte) bool {
|
||||
return c == ' ' || c == '\t'
|
||||
}
|
||||
|
||||
// Test if a character is a vertical character.
|
||||
func isverticalspace(c byte) bool {
|
||||
return c == '\n' || c == '\r' || c == '\f' || c == '\v'
|
||||
}
|
||||
|
||||
// Test if a character is letter.
|
||||
func isletter(c byte) bool {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||
}
|
||||
|
||||
// Test if a character is a letter or a digit.
|
||||
// TODO: check when this is looking for ASCII alnum and when it should use unicode
|
||||
func isalnum(c byte) bool {
|
||||
return (c >= '0' && c <= '9') || isletter(c)
|
||||
}
|
||||
|
||||
// Replace tab characters with spaces, aligning to the next TAB_SIZE column.
|
||||
// always ends output with a newline
|
||||
func expandTabs(out *bytes.Buffer, line []byte, tabSize int) {
|
||||
// first, check for common cases: no tabs, or only tabs at beginning of line
|
||||
i, prefix := 0, 0
|
||||
slowcase := false
|
||||
for i = 0; i < len(line); i++ {
|
||||
if line[i] == '\t' {
|
||||
if prefix == i {
|
||||
prefix++
|
||||
} else {
|
||||
slowcase = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no need to decode runes if all tabs are at the beginning of the line
|
||||
if !slowcase {
|
||||
for i = 0; i < prefix*tabSize; i++ {
|
||||
out.WriteByte(' ')
|
||||
}
|
||||
out.Write(line[prefix:])
|
||||
return
|
||||
}
|
||||
|
||||
// the slow case: we need to count runes to figure out how
|
||||
// many spaces to insert for each tab
|
||||
column := 0
|
||||
i = 0
|
||||
for i < len(line) {
|
||||
start := i
|
||||
for i < len(line) && line[i] != '\t' {
|
||||
_, size := utf8.DecodeRune(line[i:])
|
||||
i += size
|
||||
column++
|
||||
}
|
||||
|
||||
if i > start {
|
||||
out.Write(line[start:i])
|
||||
}
|
||||
|
||||
if i >= len(line) {
|
||||
break
|
||||
}
|
||||
|
||||
for {
|
||||
out.WriteByte(' ')
|
||||
column++
|
||||
if column%tabSize == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// Find if a line counts as indented or not.
|
||||
// Returns number of characters the indent is (0 = not indented).
|
||||
func isIndented(data []byte, indentSize int) int {
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
if data[0] == '\t' {
|
||||
return 1
|
||||
}
|
||||
if len(data) < indentSize {
|
||||
return 0
|
||||
}
|
||||
for i := 0; i < indentSize; i++ {
|
||||
if data[i] != ' ' {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return indentSize
|
||||
}
|
||||
|
||||
// Create a url-safe slug for fragments
|
||||
func slugify(in []byte) []byte {
|
||||
if len(in) == 0 {
|
||||
return in
|
||||
}
|
||||
out := make([]byte, 0, len(in))
|
||||
sym := false
|
||||
|
||||
for _, ch := range in {
|
||||
if isalnum(ch) {
|
||||
sym = false
|
||||
out = append(out, ch)
|
||||
} else if sym {
|
||||
continue
|
||||
} else {
|
||||
out = append(out, '-')
|
||||
sym = true
|
||||
}
|
||||
}
|
||||
var a, b int
|
||||
var ch byte
|
||||
for a, ch = range out {
|
||||
if ch != '-' {
|
||||
break
|
||||
}
|
||||
}
|
||||
for b = len(out) - 1; b > 0; b-- {
|
||||
if out[b] != '-' {
|
||||
break
|
||||
}
|
||||
}
|
||||
return out[a : b+1]
|
||||
}
|
||||
-360
@@ -1,360 +0,0 @@
|
||||
package blackfriday
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// NodeType specifies a type of a single node of a syntax tree. Usually one
|
||||
// node (and its type) corresponds to a single markdown feature, e.g. emphasis
|
||||
// or code block.
|
||||
type NodeType int
|
||||
|
||||
// Constants for identifying different types of nodes. See NodeType.
|
||||
const (
|
||||
Document NodeType = iota
|
||||
BlockQuote
|
||||
List
|
||||
Item
|
||||
Paragraph
|
||||
Heading
|
||||
HorizontalRule
|
||||
Emph
|
||||
Strong
|
||||
Del
|
||||
Link
|
||||
Image
|
||||
Text
|
||||
HTMLBlock
|
||||
CodeBlock
|
||||
Softbreak
|
||||
Hardbreak
|
||||
Code
|
||||
HTMLSpan
|
||||
Table
|
||||
TableCell
|
||||
TableHead
|
||||
TableBody
|
||||
TableRow
|
||||
)
|
||||
|
||||
var nodeTypeNames = []string{
|
||||
Document: "Document",
|
||||
BlockQuote: "BlockQuote",
|
||||
List: "List",
|
||||
Item: "Item",
|
||||
Paragraph: "Paragraph",
|
||||
Heading: "Heading",
|
||||
HorizontalRule: "HorizontalRule",
|
||||
Emph: "Emph",
|
||||
Strong: "Strong",
|
||||
Del: "Del",
|
||||
Link: "Link",
|
||||
Image: "Image",
|
||||
Text: "Text",
|
||||
HTMLBlock: "HTMLBlock",
|
||||
CodeBlock: "CodeBlock",
|
||||
Softbreak: "Softbreak",
|
||||
Hardbreak: "Hardbreak",
|
||||
Code: "Code",
|
||||
HTMLSpan: "HTMLSpan",
|
||||
Table: "Table",
|
||||
TableCell: "TableCell",
|
||||
TableHead: "TableHead",
|
||||
TableBody: "TableBody",
|
||||
TableRow: "TableRow",
|
||||
}
|
||||
|
||||
func (t NodeType) String() string {
|
||||
return nodeTypeNames[t]
|
||||
}
|
||||
|
||||
// ListData contains fields relevant to a List and Item node type.
|
||||
type ListData struct {
|
||||
ListFlags ListType
|
||||
Tight bool // Skip <p>s around list item data if true
|
||||
BulletChar byte // '*', '+' or '-' in bullet lists
|
||||
Delimiter byte // '.' or ')' after the number in ordered lists
|
||||
RefLink []byte // If not nil, turns this list item into a footnote item and triggers different rendering
|
||||
IsFootnotesList bool // This is a list of footnotes
|
||||
}
|
||||
|
||||
// LinkData contains fields relevant to a Link node type.
|
||||
type LinkData struct {
|
||||
Destination []byte // Destination is what goes into a href
|
||||
Title []byte // Title is the tooltip thing that goes in a title attribute
|
||||
NoteID int // NoteID contains a serial number of a footnote, zero if it's not a footnote
|
||||
Footnote *Node // If it's a footnote, this is a direct link to the footnote Node. Otherwise nil.
|
||||
}
|
||||
|
||||
// CodeBlockData contains fields relevant to a CodeBlock node type.
|
||||
type CodeBlockData struct {
|
||||
IsFenced bool // Specifies whether it's a fenced code block or an indented one
|
||||
Info []byte // This holds the info string
|
||||
FenceChar byte
|
||||
FenceLength int
|
||||
FenceOffset int
|
||||
}
|
||||
|
||||
// TableCellData contains fields relevant to a TableCell node type.
|
||||
type TableCellData struct {
|
||||
IsHeader bool // This tells if it's under the header row
|
||||
Align CellAlignFlags // This holds the value for align attribute
|
||||
}
|
||||
|
||||
// HeadingData contains fields relevant to a Heading node type.
|
||||
type HeadingData struct {
|
||||
Level int // This holds the heading level number
|
||||
HeadingID string // This might hold heading ID, if present
|
||||
IsTitleblock bool // Specifies whether it's a title block
|
||||
}
|
||||
|
||||
// Node is a single element in the abstract syntax tree of the parsed document.
|
||||
// It holds connections to the structurally neighboring nodes and, for certain
|
||||
// types of nodes, additional information that might be needed when rendering.
|
||||
type Node struct {
|
||||
Type NodeType // Determines the type of the node
|
||||
Parent *Node // Points to the parent
|
||||
FirstChild *Node // Points to the first child, if any
|
||||
LastChild *Node // Points to the last child, if any
|
||||
Prev *Node // Previous sibling; nil if it's the first child
|
||||
Next *Node // Next sibling; nil if it's the last child
|
||||
|
||||
Literal []byte // Text contents of the leaf nodes
|
||||
|
||||
HeadingData // Populated if Type is Heading
|
||||
ListData // Populated if Type is List
|
||||
CodeBlockData // Populated if Type is CodeBlock
|
||||
LinkData // Populated if Type is Link
|
||||
TableCellData // Populated if Type is TableCell
|
||||
|
||||
content []byte // Markdown content of the block nodes
|
||||
open bool // Specifies an open block node that has not been finished to process yet
|
||||
}
|
||||
|
||||
// NewNode allocates a node of a specified type.
|
||||
func NewNode(typ NodeType) *Node {
|
||||
return &Node{
|
||||
Type: typ,
|
||||
open: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) String() string {
|
||||
ellipsis := ""
|
||||
snippet := n.Literal
|
||||
if len(snippet) > 16 {
|
||||
snippet = snippet[:16]
|
||||
ellipsis = "..."
|
||||
}
|
||||
return fmt.Sprintf("%s: '%s%s'", n.Type, snippet, ellipsis)
|
||||
}
|
||||
|
||||
// Unlink removes node 'n' from the tree.
|
||||
// It panics if the node is nil.
|
||||
func (n *Node) Unlink() {
|
||||
if n.Prev != nil {
|
||||
n.Prev.Next = n.Next
|
||||
} else if n.Parent != nil {
|
||||
n.Parent.FirstChild = n.Next
|
||||
}
|
||||
if n.Next != nil {
|
||||
n.Next.Prev = n.Prev
|
||||
} else if n.Parent != nil {
|
||||
n.Parent.LastChild = n.Prev
|
||||
}
|
||||
n.Parent = nil
|
||||
n.Next = nil
|
||||
n.Prev = nil
|
||||
}
|
||||
|
||||
// AppendChild adds a node 'child' as a child of 'n'.
|
||||
// It panics if either node is nil.
|
||||
func (n *Node) AppendChild(child *Node) {
|
||||
child.Unlink()
|
||||
child.Parent = n
|
||||
if n.LastChild != nil {
|
||||
n.LastChild.Next = child
|
||||
child.Prev = n.LastChild
|
||||
n.LastChild = child
|
||||
} else {
|
||||
n.FirstChild = child
|
||||
n.LastChild = child
|
||||
}
|
||||
}
|
||||
|
||||
// InsertBefore inserts 'sibling' immediately before 'n'.
|
||||
// It panics if either node is nil.
|
||||
func (n *Node) InsertBefore(sibling *Node) {
|
||||
sibling.Unlink()
|
||||
sibling.Prev = n.Prev
|
||||
if sibling.Prev != nil {
|
||||
sibling.Prev.Next = sibling
|
||||
}
|
||||
sibling.Next = n
|
||||
n.Prev = sibling
|
||||
sibling.Parent = n.Parent
|
||||
if sibling.Prev == nil {
|
||||
sibling.Parent.FirstChild = sibling
|
||||
}
|
||||
}
|
||||
|
||||
// IsContainer returns true if 'n' can contain children.
|
||||
func (n *Node) IsContainer() bool {
|
||||
switch n.Type {
|
||||
case Document:
|
||||
fallthrough
|
||||
case BlockQuote:
|
||||
fallthrough
|
||||
case List:
|
||||
fallthrough
|
||||
case Item:
|
||||
fallthrough
|
||||
case Paragraph:
|
||||
fallthrough
|
||||
case Heading:
|
||||
fallthrough
|
||||
case Emph:
|
||||
fallthrough
|
||||
case Strong:
|
||||
fallthrough
|
||||
case Del:
|
||||
fallthrough
|
||||
case Link:
|
||||
fallthrough
|
||||
case Image:
|
||||
fallthrough
|
||||
case Table:
|
||||
fallthrough
|
||||
case TableHead:
|
||||
fallthrough
|
||||
case TableBody:
|
||||
fallthrough
|
||||
case TableRow:
|
||||
fallthrough
|
||||
case TableCell:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsLeaf returns true if 'n' is a leaf node.
|
||||
func (n *Node) IsLeaf() bool {
|
||||
return !n.IsContainer()
|
||||
}
|
||||
|
||||
func (n *Node) canContain(t NodeType) bool {
|
||||
if n.Type == List {
|
||||
return t == Item
|
||||
}
|
||||
if n.Type == Document || n.Type == BlockQuote || n.Type == Item {
|
||||
return t != Item
|
||||
}
|
||||
if n.Type == Table {
|
||||
return t == TableHead || t == TableBody
|
||||
}
|
||||
if n.Type == TableHead || n.Type == TableBody {
|
||||
return t == TableRow
|
||||
}
|
||||
if n.Type == TableRow {
|
||||
return t == TableCell
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// WalkStatus allows NodeVisitor to have some control over the tree traversal.
|
||||
// It is returned from NodeVisitor and different values allow Node.Walk to
|
||||
// decide which node to go to next.
|
||||
type WalkStatus int
|
||||
|
||||
const (
|
||||
// GoToNext is the default traversal of every node.
|
||||
GoToNext WalkStatus = iota
|
||||
// SkipChildren tells walker to skip all children of current node.
|
||||
SkipChildren
|
||||
// Terminate tells walker to terminate the traversal.
|
||||
Terminate
|
||||
)
|
||||
|
||||
// NodeVisitor is a callback to be called when traversing the syntax tree.
|
||||
// Called twice for every node: once with entering=true when the branch is
|
||||
// first visited, then with entering=false after all the children are done.
|
||||
type NodeVisitor func(node *Node, entering bool) WalkStatus
|
||||
|
||||
// Walk is a convenience method that instantiates a walker and starts a
|
||||
// traversal of subtree rooted at n.
|
||||
func (n *Node) Walk(visitor NodeVisitor) {
|
||||
w := newNodeWalker(n)
|
||||
for w.current != nil {
|
||||
status := visitor(w.current, w.entering)
|
||||
switch status {
|
||||
case GoToNext:
|
||||
w.next()
|
||||
case SkipChildren:
|
||||
w.entering = false
|
||||
w.next()
|
||||
case Terminate:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type nodeWalker struct {
|
||||
current *Node
|
||||
root *Node
|
||||
entering bool
|
||||
}
|
||||
|
||||
func newNodeWalker(root *Node) *nodeWalker {
|
||||
return &nodeWalker{
|
||||
current: root,
|
||||
root: root,
|
||||
entering: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (nw *nodeWalker) next() {
|
||||
if (!nw.current.IsContainer() || !nw.entering) && nw.current == nw.root {
|
||||
nw.current = nil
|
||||
return
|
||||
}
|
||||
if nw.entering && nw.current.IsContainer() {
|
||||
if nw.current.FirstChild != nil {
|
||||
nw.current = nw.current.FirstChild
|
||||
nw.entering = true
|
||||
} else {
|
||||
nw.entering = false
|
||||
}
|
||||
} else if nw.current.Next == nil {
|
||||
nw.current = nw.current.Parent
|
||||
nw.entering = false
|
||||
} else {
|
||||
nw.current = nw.current.Next
|
||||
nw.entering = true
|
||||
}
|
||||
}
|
||||
|
||||
func dump(ast *Node) {
|
||||
fmt.Println(dumpString(ast))
|
||||
}
|
||||
|
||||
func dumpR(ast *Node, depth int) string {
|
||||
if ast == nil {
|
||||
return ""
|
||||
}
|
||||
indent := bytes.Repeat([]byte("\t"), depth)
|
||||
content := ast.Literal
|
||||
if content == nil {
|
||||
content = ast.content
|
||||
}
|
||||
result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content)
|
||||
for n := ast.FirstChild; n != nil; n = n.Next {
|
||||
result += dumpR(n, depth+1)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func dumpString(ast *Node) string {
|
||||
return dumpR(ast, 0)
|
||||
}
|
||||
-457
@@ -1,457 +0,0 @@
|
||||
//
|
||||
// Blackfriday Markdown Processor
|
||||
// Available at http://github.com/russross/blackfriday
|
||||
//
|
||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||
// Distributed under the Simplified BSD License.
|
||||
// See README.md for details.
|
||||
//
|
||||
|
||||
//
|
||||
//
|
||||
// SmartyPants rendering
|
||||
//
|
||||
//
|
||||
|
||||
package blackfriday
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
// SPRenderer is a struct containing state of a Smartypants renderer.
|
||||
type SPRenderer struct {
|
||||
inSingleQuote bool
|
||||
inDoubleQuote bool
|
||||
callbacks [256]smartCallback
|
||||
}
|
||||
|
||||
func wordBoundary(c byte) bool {
|
||||
return c == 0 || isspace(c) || ispunct(c)
|
||||
}
|
||||
|
||||
func tolower(c byte) byte {
|
||||
if c >= 'A' && c <= 'Z' {
|
||||
return c - 'A' + 'a'
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func isdigit(c byte) bool {
|
||||
return c >= '0' && c <= '9'
|
||||
}
|
||||
|
||||
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool, addNBSP bool) bool {
|
||||
// edge of the buffer is likely to be a tag that we don't get to see,
|
||||
// so we treat it like text sometimes
|
||||
|
||||
// enumerate all sixteen possibilities for (previousChar, nextChar)
|
||||
// each can be one of {0, space, punct, other}
|
||||
switch {
|
||||
case previousChar == 0 && nextChar == 0:
|
||||
// context is not any help here, so toggle
|
||||
*isOpen = !*isOpen
|
||||
case isspace(previousChar) && nextChar == 0:
|
||||
// [ "] might be [ "<code>foo...]
|
||||
*isOpen = true
|
||||
case ispunct(previousChar) && nextChar == 0:
|
||||
// [!"] hmm... could be [Run!"] or [("<code>...]
|
||||
*isOpen = false
|
||||
case /* isnormal(previousChar) && */ nextChar == 0:
|
||||
// [a"] is probably a close
|
||||
*isOpen = false
|
||||
case previousChar == 0 && isspace(nextChar):
|
||||
// [" ] might be [...foo</code>" ]
|
||||
*isOpen = false
|
||||
case isspace(previousChar) && isspace(nextChar):
|
||||
// [ " ] context is not any help here, so toggle
|
||||
*isOpen = !*isOpen
|
||||
case ispunct(previousChar) && isspace(nextChar):
|
||||
// [!" ] is probably a close
|
||||
*isOpen = false
|
||||
case /* isnormal(previousChar) && */ isspace(nextChar):
|
||||
// [a" ] this is one of the easy cases
|
||||
*isOpen = false
|
||||
case previousChar == 0 && ispunct(nextChar):
|
||||
// ["!] hmm... could be ["$1.95] or [</code>"!...]
|
||||
*isOpen = false
|
||||
case isspace(previousChar) && ispunct(nextChar):
|
||||
// [ "!] looks more like [ "$1.95]
|
||||
*isOpen = true
|
||||
case ispunct(previousChar) && ispunct(nextChar):
|
||||
// [!"!] context is not any help here, so toggle
|
||||
*isOpen = !*isOpen
|
||||
case /* isnormal(previousChar) && */ ispunct(nextChar):
|
||||
// [a"!] is probably a close
|
||||
*isOpen = false
|
||||
case previousChar == 0 /* && isnormal(nextChar) */ :
|
||||
// ["a] is probably an open
|
||||
*isOpen = true
|
||||
case isspace(previousChar) /* && isnormal(nextChar) */ :
|
||||
// [ "a] this is one of the easy cases
|
||||
*isOpen = true
|
||||
case ispunct(previousChar) /* && isnormal(nextChar) */ :
|
||||
// [!"a] is probably an open
|
||||
*isOpen = true
|
||||
default:
|
||||
// [a'b] maybe a contraction?
|
||||
*isOpen = false
|
||||
}
|
||||
|
||||
// Note that with the limited lookahead, this non-breaking
|
||||
// space will also be appended to single double quotes.
|
||||
if addNBSP && !*isOpen {
|
||||
out.WriteString(" ")
|
||||
}
|
||||
|
||||
out.WriteByte('&')
|
||||
if *isOpen {
|
||||
out.WriteByte('l')
|
||||
} else {
|
||||
out.WriteByte('r')
|
||||
}
|
||||
out.WriteByte(quote)
|
||||
out.WriteString("quo;")
|
||||
|
||||
if addNBSP && *isOpen {
|
||||
out.WriteString(" ")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||
if len(text) >= 2 {
|
||||
t1 := tolower(text[1])
|
||||
|
||||
if t1 == '\'' {
|
||||
nextChar := byte(0)
|
||||
if len(text) >= 3 {
|
||||
nextChar = text[2]
|
||||
}
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
if (t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && (len(text) < 3 || wordBoundary(text[2])) {
|
||||
out.WriteString("’")
|
||||
return 0
|
||||
}
|
||||
|
||||
if len(text) >= 3 {
|
||||
t2 := tolower(text[2])
|
||||
|
||||
if ((t1 == 'r' && t2 == 'e') || (t1 == 'l' && t2 == 'l') || (t1 == 'v' && t2 == 'e')) &&
|
||||
(len(text) < 4 || wordBoundary(text[3])) {
|
||||
out.WriteString("’")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextChar := byte(0)
|
||||
if len(text) > 1 {
|
||||
nextChar = text[1]
|
||||
}
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 's', &r.inSingleQuote, false) {
|
||||
return 0
|
||||
}
|
||||
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *SPRenderer) smartParens(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||
if len(text) >= 3 {
|
||||
t1 := tolower(text[1])
|
||||
t2 := tolower(text[2])
|
||||
|
||||
if t1 == 'c' && t2 == ')' {
|
||||
out.WriteString("©")
|
||||
return 2
|
||||
}
|
||||
|
||||
if t1 == 'r' && t2 == ')' {
|
||||
out.WriteString("®")
|
||||
return 2
|
||||
}
|
||||
|
||||
if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' {
|
||||
out.WriteString("™")
|
||||
return 3
|
||||
}
|
||||
}
|
||||
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *SPRenderer) smartDash(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||
if len(text) >= 2 {
|
||||
if text[1] == '-' {
|
||||
out.WriteString("—")
|
||||
return 1
|
||||
}
|
||||
|
||||
if wordBoundary(previousChar) && wordBoundary(text[1]) {
|
||||
out.WriteString("–")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *SPRenderer) smartDashLatex(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||
if len(text) >= 3 && text[1] == '-' && text[2] == '-' {
|
||||
out.WriteString("—")
|
||||
return 2
|
||||
}
|
||||
if len(text) >= 2 && text[1] == '-' {
|
||||
out.WriteString("–")
|
||||
return 1
|
||||
}
|
||||
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte, addNBSP bool) int {
|
||||
if bytes.HasPrefix(text, []byte(""")) {
|
||||
nextChar := byte(0)
|
||||
if len(text) >= 7 {
|
||||
nextChar = text[6]
|
||||
}
|
||||
if smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, addNBSP) {
|
||||
return 5
|
||||
}
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(text, []byte("�")) {
|
||||
return 3
|
||||
}
|
||||
|
||||
out.WriteByte('&')
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *SPRenderer) smartAmp(angledQuotes, addNBSP bool) func(*bytes.Buffer, byte, []byte) int {
|
||||
var quote byte = 'd'
|
||||
if angledQuotes {
|
||||
quote = 'a'
|
||||
}
|
||||
|
||||
return func(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||
return r.smartAmpVariant(out, previousChar, text, quote, addNBSP)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SPRenderer) smartPeriod(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||
if len(text) >= 3 && text[1] == '.' && text[2] == '.' {
|
||||
out.WriteString("…")
|
||||
return 2
|
||||
}
|
||||
|
||||
if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' {
|
||||
out.WriteString("…")
|
||||
return 4
|
||||
}
|
||||
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *SPRenderer) smartBacktick(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||
if len(text) >= 2 && text[1] == '`' {
|
||||
nextChar := byte(0)
|
||||
if len(text) >= 3 {
|
||||
nextChar = text[2]
|
||||
}
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *SPRenderer) smartNumberGeneric(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
|
||||
// is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b
|
||||
// note: check for regular slash (/) or fraction slash (⁄, 0x2044, or 0xe2 81 84 in utf-8)
|
||||
// and avoid changing dates like 1/23/2005 into fractions.
|
||||
numEnd := 0
|
||||
for len(text) > numEnd && isdigit(text[numEnd]) {
|
||||
numEnd++
|
||||
}
|
||||
if numEnd == 0 {
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
denStart := numEnd + 1
|
||||
if len(text) > numEnd+3 && text[numEnd] == 0xe2 && text[numEnd+1] == 0x81 && text[numEnd+2] == 0x84 {
|
||||
denStart = numEnd + 3
|
||||
} else if len(text) < numEnd+2 || text[numEnd] != '/' {
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
denEnd := denStart
|
||||
for len(text) > denEnd && isdigit(text[denEnd]) {
|
||||
denEnd++
|
||||
}
|
||||
if denEnd == denStart {
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
if len(text) == denEnd || wordBoundary(text[denEnd]) && text[denEnd] != '/' {
|
||||
out.WriteString("<sup>")
|
||||
out.Write(text[:numEnd])
|
||||
out.WriteString("</sup>⁄<sub>")
|
||||
out.Write(text[denStart:denEnd])
|
||||
out.WriteString("</sub>")
|
||||
return denEnd - 1
|
||||
}
|
||||
}
|
||||
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *SPRenderer) smartNumber(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
|
||||
if text[0] == '1' && text[1] == '/' && text[2] == '2' {
|
||||
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' {
|
||||
out.WriteString("½")
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
if text[0] == '1' && text[1] == '/' && text[2] == '4' {
|
||||
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h') {
|
||||
out.WriteString("¼")
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
if text[0] == '3' && text[1] == '/' && text[2] == '4' {
|
||||
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's') {
|
||||
out.WriteString("¾")
|
||||
return 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *SPRenderer) smartDoubleQuoteVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte) int {
|
||||
nextChar := byte(0)
|
||||
if len(text) > 1 {
|
||||
nextChar = text[1]
|
||||
}
|
||||
if !smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, false) {
|
||||
out.WriteString(""")
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *SPRenderer) smartDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||
return r.smartDoubleQuoteVariant(out, previousChar, text, 'd')
|
||||
}
|
||||
|
||||
func (r *SPRenderer) smartAngledDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||
return r.smartDoubleQuoteVariant(out, previousChar, text, 'a')
|
||||
}
|
||||
|
||||
func (r *SPRenderer) smartLeftAngle(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||
i := 0
|
||||
|
||||
for i < len(text) && text[i] != '>' {
|
||||
i++
|
||||
}
|
||||
|
||||
out.Write(text[:i+1])
|
||||
return i
|
||||
}
|
||||
|
||||
type smartCallback func(out *bytes.Buffer, previousChar byte, text []byte) int
|
||||
|
||||
// NewSmartypantsRenderer constructs a Smartypants renderer object.
|
||||
func NewSmartypantsRenderer(flags HTMLFlags) *SPRenderer {
|
||||
var (
|
||||
r SPRenderer
|
||||
|
||||
smartAmpAngled = r.smartAmp(true, false)
|
||||
smartAmpAngledNBSP = r.smartAmp(true, true)
|
||||
smartAmpRegular = r.smartAmp(false, false)
|
||||
smartAmpRegularNBSP = r.smartAmp(false, true)
|
||||
|
||||
addNBSP = flags&SmartypantsQuotesNBSP != 0
|
||||
)
|
||||
|
||||
if flags&SmartypantsAngledQuotes == 0 {
|
||||
r.callbacks['"'] = r.smartDoubleQuote
|
||||
if !addNBSP {
|
||||
r.callbacks['&'] = smartAmpRegular
|
||||
} else {
|
||||
r.callbacks['&'] = smartAmpRegularNBSP
|
||||
}
|
||||
} else {
|
||||
r.callbacks['"'] = r.smartAngledDoubleQuote
|
||||
if !addNBSP {
|
||||
r.callbacks['&'] = smartAmpAngled
|
||||
} else {
|
||||
r.callbacks['&'] = smartAmpAngledNBSP
|
||||
}
|
||||
}
|
||||
r.callbacks['\''] = r.smartSingleQuote
|
||||
r.callbacks['('] = r.smartParens
|
||||
if flags&SmartypantsDashes != 0 {
|
||||
if flags&SmartypantsLatexDashes == 0 {
|
||||
r.callbacks['-'] = r.smartDash
|
||||
} else {
|
||||
r.callbacks['-'] = r.smartDashLatex
|
||||
}
|
||||
}
|
||||
r.callbacks['.'] = r.smartPeriod
|
||||
if flags&SmartypantsFractions == 0 {
|
||||
r.callbacks['1'] = r.smartNumber
|
||||
r.callbacks['3'] = r.smartNumber
|
||||
} else {
|
||||
for ch := '1'; ch <= '9'; ch++ {
|
||||
r.callbacks[ch] = r.smartNumberGeneric
|
||||
}
|
||||
}
|
||||
r.callbacks['<'] = r.smartLeftAngle
|
||||
r.callbacks['`'] = r.smartBacktick
|
||||
return &r
|
||||
}
|
||||
|
||||
// Process is the entry point of the Smartypants renderer.
|
||||
func (r *SPRenderer) Process(w io.Writer, text []byte) {
|
||||
mark := 0
|
||||
for i := 0; i < len(text); i++ {
|
||||
if action := r.callbacks[text[i]]; action != nil {
|
||||
if i > mark {
|
||||
w.Write(text[mark:i])
|
||||
}
|
||||
previousChar := byte(0)
|
||||
if i > 0 {
|
||||
previousChar = text[i-1]
|
||||
}
|
||||
var tmp bytes.Buffer
|
||||
i += action(&tmp, previousChar, text[i:])
|
||||
w.Write(tmp.Bytes())
|
||||
mark = i + 1
|
||||
}
|
||||
}
|
||||
if mark < len(text) {
|
||||
w.Write(text[mark:])
|
||||
}
|
||||
}
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
[flake8]
|
||||
max-line-length = 120
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
cli
|
||||
===
|
||||
|
||||
[](https://github.com/urfave/cli/actions/workflows/cli.yml)
|
||||
[](https://pkg.go.dev/github.com/urfave/cli/)
|
||||
[](https://goreportcard.com/report/urfave/cli)
|
||||
[](https://codecov.io/gh/urfave/cli)
|
||||
|
||||
cli is a simple, fast, and fun package for building command line apps in Go. The
|
||||
goal is to enable developers to write fast and distributable command line
|
||||
applications in an expressive way.
|
||||
|
||||
## Usage Documentation
|
||||
|
||||
Usage documentation for `v1` is available [at the docs
|
||||
site](https://cli.urfave.org/v1/getting-started/) or in-tree at
|
||||
[./docs/v1/manual.md](./docs/v1/manual.md)
|
||||
|
||||
## Installation
|
||||
|
||||
Make sure you have a working Go environment. Go version 1.18+ is supported.
|
||||
|
||||
### Supported platforms
|
||||
|
||||
cli is tested against multiple versions of Go on Linux, and against the latest released
|
||||
version of Go on OS X and Windows. For full details, see
|
||||
[./.github/workflows/cli.yml](./.github/workflows/cli.yml).
|
||||
|
||||
### Build tags
|
||||
|
||||
You can use the following build tags:
|
||||
|
||||
#### `urfave_cli_no_docs`
|
||||
|
||||
When set, this removes `ToMarkdown` and `ToMan` methods, so your application
|
||||
won't be able to call those. This reduces the resulting binary size by about
|
||||
300-400 KB (measured using Go 1.18.1 on Linux/amd64), due to less dependencies.
|
||||
|
||||
### Using `v1` releases
|
||||
|
||||
```
|
||||
$ go get github.com/urfave/cli
|
||||
```
|
||||
|
||||
```go
|
||||
...
|
||||
import (
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
...
|
||||
```
|
||||
-531
@@ -1,531 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md"
|
||||
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
|
||||
// unused variable. commented for now. will remove in future if agreed upon by everyone
|
||||
//runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL)
|
||||
|
||||
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
|
||||
|
||||
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
|
||||
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
|
||||
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
|
||||
)
|
||||
|
||||
// App is the main structure of a cli application. It is recommended that
|
||||
// an app be created with the cli.NewApp() function
|
||||
type App struct {
|
||||
// The name of the program. Defaults to path.Base(os.Args[0])
|
||||
Name string
|
||||
// Full name of command for help, defaults to Name
|
||||
HelpName string
|
||||
// Description of the program.
|
||||
Usage string
|
||||
// Text to override the USAGE section of help
|
||||
UsageText string
|
||||
// Description of the program argument format.
|
||||
ArgsUsage string
|
||||
// Version of the program
|
||||
Version string
|
||||
// Description of the program
|
||||
Description string
|
||||
// List of commands to execute
|
||||
Commands []Command
|
||||
// List of flags to parse
|
||||
Flags []Flag
|
||||
// Boolean to enable bash completion commands
|
||||
EnableBashCompletion bool
|
||||
// Boolean to hide built-in help command
|
||||
HideHelp bool
|
||||
// Boolean to hide built-in version flag and the VERSION section of help
|
||||
HideVersion bool
|
||||
// Populate on app startup, only gettable through method Categories()
|
||||
categories CommandCategories
|
||||
// An action to execute when the bash-completion flag is set
|
||||
BashComplete BashCompleteFunc
|
||||
// An action to execute before any subcommands are run, but after the context is ready
|
||||
// If a non-nil error is returned, no subcommands are run
|
||||
Before BeforeFunc
|
||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
||||
// It is run even if Action() panics
|
||||
After AfterFunc
|
||||
|
||||
// The action to execute when no subcommands are specified
|
||||
// Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}`
|
||||
// *Note*: support for the deprecated `Action` signature will be removed in a future version
|
||||
Action interface{}
|
||||
|
||||
// Execute this function if the proper command cannot be found
|
||||
CommandNotFound CommandNotFoundFunc
|
||||
// Execute this function if an usage error occurs
|
||||
OnUsageError OnUsageErrorFunc
|
||||
// Compilation date
|
||||
Compiled time.Time
|
||||
// List of all authors who contributed
|
||||
Authors []Author
|
||||
// Copyright of the binary if any
|
||||
Copyright string
|
||||
// Name of Author (Note: Use App.Authors, this is deprecated)
|
||||
Author string
|
||||
// Email of Author (Note: Use App.Authors, this is deprecated)
|
||||
Email string
|
||||
// Writer writer to write output to
|
||||
Writer io.Writer
|
||||
// ErrWriter writes error output
|
||||
ErrWriter io.Writer
|
||||
// Execute this function to handle ExitErrors. If not provided, HandleExitCoder is provided to
|
||||
// function as a default, so this is optional.
|
||||
ExitErrHandler ExitErrHandlerFunc
|
||||
// Other custom info
|
||||
Metadata map[string]interface{}
|
||||
// Carries a function which returns app specific info.
|
||||
ExtraInfo func() map[string]string
|
||||
// CustomAppHelpTemplate the text template for app help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
CustomAppHelpTemplate string
|
||||
// Boolean to enable short-option handling so user can combine several
|
||||
// single-character bool arguements into one
|
||||
// i.e. foobar -o -v -> foobar -ov
|
||||
UseShortOptionHandling bool
|
||||
|
||||
didSetup bool
|
||||
}
|
||||
|
||||
// Tries to find out when this binary was compiled.
|
||||
// Returns the current time if it fails to find it.
|
||||
func compileTime() time.Time {
|
||||
info, err := os.Stat(os.Args[0])
|
||||
if err != nil {
|
||||
return time.Now()
|
||||
}
|
||||
return info.ModTime()
|
||||
}
|
||||
|
||||
// NewApp creates a new cli Application with some reasonable defaults for Name,
|
||||
// Usage, Version and Action.
|
||||
func NewApp() *App {
|
||||
return &App{
|
||||
Name: filepath.Base(os.Args[0]),
|
||||
HelpName: filepath.Base(os.Args[0]),
|
||||
Usage: "A new cli application",
|
||||
UsageText: "",
|
||||
BashComplete: DefaultAppComplete,
|
||||
Action: helpCommand.Action,
|
||||
Compiled: compileTime(),
|
||||
Writer: os.Stdout,
|
||||
}
|
||||
}
|
||||
|
||||
// Setup runs initialization code to ensure all data structures are ready for
|
||||
// `Run` or inspection prior to `Run`. It is internally called by `Run`, but
|
||||
// will return early if setup has already happened.
|
||||
func (a *App) Setup() {
|
||||
if a.didSetup {
|
||||
return
|
||||
}
|
||||
|
||||
a.didSetup = true
|
||||
|
||||
if a.Author != "" || a.Email != "" {
|
||||
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
|
||||
}
|
||||
|
||||
var newCmds []Command
|
||||
for _, c := range a.Commands {
|
||||
if c.HelpName == "" {
|
||||
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
|
||||
}
|
||||
newCmds = append(newCmds, c)
|
||||
}
|
||||
a.Commands = newCmds
|
||||
|
||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||
a.Commands = append(a.Commands, helpCommand)
|
||||
if (HelpFlag != BoolFlag{}) {
|
||||
a.appendFlag(HelpFlag)
|
||||
}
|
||||
}
|
||||
|
||||
if a.Version == "" {
|
||||
a.HideVersion = true
|
||||
}
|
||||
|
||||
if !a.HideVersion {
|
||||
a.appendFlag(VersionFlag)
|
||||
}
|
||||
|
||||
a.categories = CommandCategories{}
|
||||
for _, command := range a.Commands {
|
||||
a.categories = a.categories.AddCommand(command.Category, command)
|
||||
}
|
||||
sort.Sort(a.categories)
|
||||
|
||||
if a.Metadata == nil {
|
||||
a.Metadata = make(map[string]interface{})
|
||||
}
|
||||
|
||||
if a.Writer == nil {
|
||||
a.Writer = os.Stdout
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) newFlagSet() (*flag.FlagSet, error) {
|
||||
return flagSet(a.Name, a.Flags)
|
||||
}
|
||||
|
||||
func (a *App) useShortOptionHandling() bool {
|
||||
return a.UseShortOptionHandling
|
||||
}
|
||||
|
||||
// Run is the entry point to the cli app. Parses the arguments slice and routes
|
||||
// to the proper flag/args combination
|
||||
func (a *App) Run(arguments []string) (err error) {
|
||||
a.Setup()
|
||||
|
||||
// handle the completion flag separately from the flagset since
|
||||
// completion could be attempted after a flag, but before its value was put
|
||||
// on the command line. this causes the flagset to interpret the completion
|
||||
// flag name as the value of the flag before it which is undesirable
|
||||
// note that we can only do this because the shell autocomplete function
|
||||
// always appends the completion flag at the end of the command
|
||||
shellComplete, arguments := checkShellCompleteFlag(a, arguments)
|
||||
|
||||
set, err := a.newFlagSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = parseIter(set, a, arguments[1:], shellComplete)
|
||||
nerr := normalizeFlags(a.Flags, set)
|
||||
context := NewContext(a, set, nil)
|
||||
if nerr != nil {
|
||||
_, _ = fmt.Fprintln(a.Writer, nerr)
|
||||
_ = ShowAppHelp(context)
|
||||
return nerr
|
||||
}
|
||||
context.shellComplete = shellComplete
|
||||
|
||||
if checkCompletions(context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if a.OnUsageError != nil {
|
||||
err := a.OnUsageError(context, err, false)
|
||||
a.handleExitCoder(context, err)
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
||||
_ = ShowAppHelp(context)
|
||||
return err
|
||||
}
|
||||
|
||||
if !a.HideHelp && checkHelp(context) {
|
||||
_ = ShowAppHelp(context)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !a.HideVersion && checkVersion(context) {
|
||||
ShowVersion(context)
|
||||
return nil
|
||||
}
|
||||
|
||||
cerr := checkRequiredFlags(a.Flags, context)
|
||||
if cerr != nil {
|
||||
_ = ShowAppHelp(context)
|
||||
return cerr
|
||||
}
|
||||
|
||||
if a.After != nil && !context.shellComplete {
|
||||
defer func() {
|
||||
if afterErr := a.After(context); afterErr != nil {
|
||||
if err != nil {
|
||||
err = NewMultiError(err, afterErr)
|
||||
} else {
|
||||
err = afterErr
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if a.Before != nil && !context.shellComplete {
|
||||
beforeErr := a.Before(context)
|
||||
if beforeErr != nil {
|
||||
a.handleExitCoder(context, beforeErr)
|
||||
err = beforeErr
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
args := context.Args()
|
||||
if args.Present() {
|
||||
name := args.First()
|
||||
c := a.Command(name)
|
||||
if c != nil {
|
||||
return c.Run(context)
|
||||
}
|
||||
}
|
||||
|
||||
if a.Action == nil {
|
||||
a.Action = helpCommand.Action
|
||||
}
|
||||
|
||||
// Run default Action
|
||||
err = HandleAction(a.Action, context)
|
||||
|
||||
a.handleExitCoder(context, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// RunAndExitOnError calls .Run() and exits non-zero if an error was returned
|
||||
//
|
||||
// Deprecated: instead you should return an error that fulfills cli.ExitCoder
|
||||
// to cli.App.Run. This will cause the application to exit with the given eror
|
||||
// code in the cli.ExitCoder
|
||||
func (a *App) RunAndExitOnError() {
|
||||
if err := a.Run(os.Args); err != nil {
|
||||
_, _ = fmt.Fprintln(a.errWriter(), err)
|
||||
OsExiter(1)
|
||||
}
|
||||
}
|
||||
|
||||
// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
|
||||
// generate command-specific flags
|
||||
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
// append help to commands
|
||||
if len(a.Commands) > 0 {
|
||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||
a.Commands = append(a.Commands, helpCommand)
|
||||
if (HelpFlag != BoolFlag{}) {
|
||||
a.appendFlag(HelpFlag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newCmds := []Command{}
|
||||
for _, c := range a.Commands {
|
||||
if c.HelpName == "" {
|
||||
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
|
||||
}
|
||||
newCmds = append(newCmds, c)
|
||||
}
|
||||
a.Commands = newCmds
|
||||
|
||||
set, err := a.newFlagSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = parseIter(set, a, ctx.Args().Tail(), ctx.shellComplete)
|
||||
nerr := normalizeFlags(a.Flags, set)
|
||||
context := NewContext(a, set, ctx)
|
||||
|
||||
if nerr != nil {
|
||||
_, _ = fmt.Fprintln(a.Writer, nerr)
|
||||
_, _ = fmt.Fprintln(a.Writer)
|
||||
if len(a.Commands) > 0 {
|
||||
_ = ShowSubcommandHelp(context)
|
||||
} else {
|
||||
_ = ShowCommandHelp(ctx, context.Args().First())
|
||||
}
|
||||
return nerr
|
||||
}
|
||||
|
||||
if checkCompletions(context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if a.OnUsageError != nil {
|
||||
err = a.OnUsageError(context, err, true)
|
||||
a.handleExitCoder(context, err)
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
||||
_ = ShowSubcommandHelp(context)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(a.Commands) > 0 {
|
||||
if checkSubcommandHelp(context) {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if checkCommandHelp(ctx, context.Args().First()) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
cerr := checkRequiredFlags(a.Flags, context)
|
||||
if cerr != nil {
|
||||
_ = ShowSubcommandHelp(context)
|
||||
return cerr
|
||||
}
|
||||
|
||||
if a.After != nil && !context.shellComplete {
|
||||
defer func() {
|
||||
afterErr := a.After(context)
|
||||
if afterErr != nil {
|
||||
a.handleExitCoder(context, err)
|
||||
if err != nil {
|
||||
err = NewMultiError(err, afterErr)
|
||||
} else {
|
||||
err = afterErr
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if a.Before != nil && !context.shellComplete {
|
||||
beforeErr := a.Before(context)
|
||||
if beforeErr != nil {
|
||||
a.handleExitCoder(context, beforeErr)
|
||||
err = beforeErr
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
args := context.Args()
|
||||
if args.Present() {
|
||||
name := args.First()
|
||||
c := a.Command(name)
|
||||
if c != nil {
|
||||
return c.Run(context)
|
||||
}
|
||||
}
|
||||
|
||||
// Run default Action
|
||||
err = HandleAction(a.Action, context)
|
||||
|
||||
a.handleExitCoder(context, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Command returns the named command on App. Returns nil if the command does not exist
|
||||
func (a *App) Command(name string) *Command {
|
||||
for _, c := range a.Commands {
|
||||
if c.HasName(name) {
|
||||
return &c
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Categories returns a slice containing all the categories with the commands they contain
|
||||
func (a *App) Categories() CommandCategories {
|
||||
return a.categories
|
||||
}
|
||||
|
||||
// VisibleCategories returns a slice of categories and commands that are
|
||||
// Hidden=false
|
||||
func (a *App) VisibleCategories() []*CommandCategory {
|
||||
ret := []*CommandCategory{}
|
||||
for _, category := range a.categories {
|
||||
if visible := func() *CommandCategory {
|
||||
for _, command := range category.Commands {
|
||||
if !command.Hidden {
|
||||
return category
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); visible != nil {
|
||||
ret = append(ret, visible)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
||||
func (a *App) VisibleCommands() []Command {
|
||||
var ret []Command
|
||||
for _, command := range a.Commands {
|
||||
if !command.Hidden {
|
||||
ret = append(ret, command)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
||||
func (a *App) VisibleFlags() []Flag {
|
||||
return visibleFlags(a.Flags)
|
||||
}
|
||||
|
||||
func (a *App) hasFlag(flag Flag) bool {
|
||||
for _, f := range a.Flags {
|
||||
if flag == f {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *App) errWriter() io.Writer {
|
||||
// When the app ErrWriter is nil use the package level one.
|
||||
if a.ErrWriter == nil {
|
||||
return ErrWriter
|
||||
}
|
||||
|
||||
return a.ErrWriter
|
||||
}
|
||||
|
||||
func (a *App) appendFlag(flag Flag) {
|
||||
if !a.hasFlag(flag) {
|
||||
a.Flags = append(a.Flags, flag)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) handleExitCoder(context *Context, err error) {
|
||||
if a.ExitErrHandler != nil {
|
||||
a.ExitErrHandler(context, err)
|
||||
} else {
|
||||
HandleExitCoder(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Author represents someone who has contributed to a cli project.
|
||||
type Author struct {
|
||||
Name string // The Authors name
|
||||
Email string // The Authors email
|
||||
}
|
||||
|
||||
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
|
||||
func (a Author) String() string {
|
||||
e := ""
|
||||
if a.Email != "" {
|
||||
e = " <" + a.Email + ">"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v%v", a.Name, e)
|
||||
}
|
||||
|
||||
// HandleAction attempts to figure out which Action signature was used. If
|
||||
// it's an ActionFunc or a func with the legacy signature for Action, the func
|
||||
// is run!
|
||||
func HandleAction(action interface{}, context *Context) (err error) {
|
||||
switch a := action.(type) {
|
||||
case ActionFunc:
|
||||
return a(context)
|
||||
case func(*Context) error:
|
||||
return a(context)
|
||||
case func(*Context): // deprecated function signature
|
||||
a(context)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errInvalidActionType
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
package cli
|
||||
|
||||
// CommandCategories is a slice of *CommandCategory.
|
||||
type CommandCategories []*CommandCategory
|
||||
|
||||
// CommandCategory is a category containing commands.
|
||||
type CommandCategory struct {
|
||||
Name string
|
||||
Commands Commands
|
||||
}
|
||||
|
||||
func (c CommandCategories) Less(i, j int) bool {
|
||||
return lexicographicLess(c[i].Name, c[j].Name)
|
||||
}
|
||||
|
||||
func (c CommandCategories) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
func (c CommandCategories) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
|
||||
// AddCommand adds a command to a category.
|
||||
func (c CommandCategories) AddCommand(category string, command Command) CommandCategories {
|
||||
for _, commandCategory := range c {
|
||||
if commandCategory.Name == category {
|
||||
commandCategory.Commands = append(commandCategory.Commands, command)
|
||||
return c
|
||||
}
|
||||
}
|
||||
return append(c, &CommandCategory{Name: category, Commands: []Command{command}})
|
||||
}
|
||||
|
||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
||||
func (c *CommandCategory) VisibleCommands() []Command {
|
||||
ret := []Command{}
|
||||
for _, command := range c.Commands {
|
||||
if !command.Hidden {
|
||||
ret = append(ret, command)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
// Package cli provides a minimal framework for creating and organizing command line
|
||||
// Go applications. cli is designed to be easy to understand and write, the most simple
|
||||
// cli application can be written as follows:
|
||||
// func main() {
|
||||
// cli.NewApp().Run(os.Args)
|
||||
// }
|
||||
//
|
||||
// Of course this application does not do much, so let's make this an actual application:
|
||||
// func main() {
|
||||
// app := cli.NewApp()
|
||||
// app.Name = "greet"
|
||||
// app.Usage = "say a greeting"
|
||||
// app.Action = func(c *cli.Context) error {
|
||||
// println("Greetings")
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// app.Run(os.Args)
|
||||
// }
|
||||
package cli
|
||||
|
||||
//go:generate go run flag-gen/main.go flag-gen/assets_vfsdata.go
|
||||
-386
@@ -1,386 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Command is a subcommand for a cli.App.
|
||||
type Command struct {
|
||||
// The name of the command
|
||||
Name string
|
||||
// short name of the command. Typically one character (deprecated, use `Aliases`)
|
||||
ShortName string
|
||||
// A list of aliases for the command
|
||||
Aliases []string
|
||||
// A short description of the usage of this command
|
||||
Usage string
|
||||
// Custom text to show on USAGE section of help
|
||||
UsageText string
|
||||
// A longer explanation of how the command works
|
||||
Description string
|
||||
// A short description of the arguments of this command
|
||||
ArgsUsage string
|
||||
// The category the command is part of
|
||||
Category string
|
||||
// The function to call when checking for bash command completions
|
||||
BashComplete BashCompleteFunc
|
||||
// An action to execute before any sub-subcommands are run, but after the context is ready
|
||||
// If a non-nil error is returned, no sub-subcommands are run
|
||||
Before BeforeFunc
|
||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
||||
// It is run even if Action() panics
|
||||
After AfterFunc
|
||||
// The function to call when this command is invoked
|
||||
Action interface{}
|
||||
// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
|
||||
// of deprecation period has passed, maybe?
|
||||
|
||||
// Execute this function if a usage error occurs.
|
||||
OnUsageError OnUsageErrorFunc
|
||||
// List of child commands
|
||||
Subcommands Commands
|
||||
// List of flags to parse
|
||||
Flags []Flag
|
||||
// Treat all flags as normal arguments if true
|
||||
SkipFlagParsing bool
|
||||
// Skip argument reordering which attempts to move flags before arguments,
|
||||
// but only works if all flags appear after all arguments. This behavior was
|
||||
// removed n version 2 since it only works under specific conditions so we
|
||||
// backport here by exposing it as an option for compatibility.
|
||||
SkipArgReorder bool
|
||||
// Boolean to hide built-in help command
|
||||
HideHelp bool
|
||||
// Boolean to hide this command from help or completion
|
||||
Hidden bool
|
||||
// Boolean to enable short-option handling so user can combine several
|
||||
// single-character bool arguments into one
|
||||
// i.e. foobar -o -v -> foobar -ov
|
||||
UseShortOptionHandling bool
|
||||
|
||||
// Full name of command for help, defaults to full command name, including parent commands.
|
||||
HelpName string
|
||||
commandNamePath []string
|
||||
|
||||
// CustomHelpTemplate the text template for the command help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
CustomHelpTemplate string
|
||||
}
|
||||
|
||||
type CommandsByName []Command
|
||||
|
||||
func (c CommandsByName) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
func (c CommandsByName) Less(i, j int) bool {
|
||||
return lexicographicLess(c[i].Name, c[j].Name)
|
||||
}
|
||||
|
||||
func (c CommandsByName) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
|
||||
// FullName returns the full name of the command.
|
||||
// For subcommands this ensures that parent commands are part of the command path
|
||||
func (c Command) FullName() string {
|
||||
if c.commandNamePath == nil {
|
||||
return c.Name
|
||||
}
|
||||
return strings.Join(c.commandNamePath, " ")
|
||||
}
|
||||
|
||||
// Commands is a slice of Command
|
||||
type Commands []Command
|
||||
|
||||
// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
||||
func (c Command) Run(ctx *Context) (err error) {
|
||||
if !c.SkipFlagParsing {
|
||||
if len(c.Subcommands) > 0 {
|
||||
return c.startApp(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
|
||||
// append help to flags
|
||||
c.Flags = append(
|
||||
c.Flags,
|
||||
HelpFlag,
|
||||
)
|
||||
}
|
||||
|
||||
if ctx.App.UseShortOptionHandling {
|
||||
c.UseShortOptionHandling = true
|
||||
}
|
||||
|
||||
set, err := c.parseFlags(ctx.Args().Tail(), ctx.shellComplete)
|
||||
|
||||
context := NewContext(ctx.App, set, ctx)
|
||||
context.Command = c
|
||||
if checkCommandCompletions(context, c.Name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if c.OnUsageError != nil {
|
||||
err := c.OnUsageError(context, err, false)
|
||||
context.App.handleExitCoder(context, err)
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
|
||||
_, _ = fmt.Fprintln(context.App.Writer)
|
||||
_ = ShowCommandHelp(context, c.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
if checkCommandHelp(context, c.Name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
cerr := checkRequiredFlags(c.Flags, context)
|
||||
if cerr != nil {
|
||||
_ = ShowCommandHelp(context, c.Name)
|
||||
return cerr
|
||||
}
|
||||
|
||||
if c.After != nil {
|
||||
defer func() {
|
||||
afterErr := c.After(context)
|
||||
if afterErr != nil {
|
||||
context.App.handleExitCoder(context, err)
|
||||
if err != nil {
|
||||
err = NewMultiError(err, afterErr)
|
||||
} else {
|
||||
err = afterErr
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if c.Before != nil {
|
||||
err = c.Before(context)
|
||||
if err != nil {
|
||||
context.App.handleExitCoder(context, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.Action == nil {
|
||||
c.Action = helpSubcommand.Action
|
||||
}
|
||||
|
||||
err = HandleAction(c.Action, context)
|
||||
|
||||
if err != nil {
|
||||
context.App.handleExitCoder(context, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Command) parseFlags(args Args, shellComplete bool) (*flag.FlagSet, error) {
|
||||
if c.SkipFlagParsing {
|
||||
set, err := c.newFlagSet()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return set, set.Parse(append([]string{"--"}, args...))
|
||||
}
|
||||
|
||||
if !c.SkipArgReorder {
|
||||
args = reorderArgs(c.Flags, args)
|
||||
}
|
||||
|
||||
set, err := c.newFlagSet()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = parseIter(set, c, args, shellComplete)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = normalizeFlags(c.Flags, set)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return set, nil
|
||||
}
|
||||
|
||||
func (c *Command) newFlagSet() (*flag.FlagSet, error) {
|
||||
return flagSet(c.Name, c.Flags)
|
||||
}
|
||||
|
||||
func (c *Command) useShortOptionHandling() bool {
|
||||
return c.UseShortOptionHandling
|
||||
}
|
||||
|
||||
// reorderArgs moves all flags (via reorderedArgs) before the rest of
|
||||
// the arguments (remainingArgs) as this is what flag expects.
|
||||
func reorderArgs(commandFlags []Flag, args []string) []string {
|
||||
var remainingArgs, reorderedArgs []string
|
||||
|
||||
nextIndexMayContainValue := false
|
||||
for i, arg := range args {
|
||||
|
||||
// if we're expecting an option-value, check if this arg is a value, in
|
||||
// which case it should be re-ordered next to its associated flag
|
||||
if nextIndexMayContainValue && !argIsFlag(commandFlags, arg) {
|
||||
nextIndexMayContainValue = false
|
||||
reorderedArgs = append(reorderedArgs, arg)
|
||||
} else if arg == "--" {
|
||||
// don't reorder any args after the -- delimiter As described in the POSIX spec:
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02
|
||||
// > Guideline 10:
|
||||
// > The first -- argument that is not an option-argument should be accepted
|
||||
// > as a delimiter indicating the end of options. Any following arguments
|
||||
// > should be treated as operands, even if they begin with the '-' character.
|
||||
|
||||
// make sure the "--" delimiter itself is at the start
|
||||
remainingArgs = append([]string{"--"}, remainingArgs...)
|
||||
remainingArgs = append(remainingArgs, args[i+1:]...)
|
||||
break
|
||||
// checks if this is an arg that should be re-ordered
|
||||
} else if argIsFlag(commandFlags, arg) {
|
||||
// we have determined that this is a flag that we should re-order
|
||||
reorderedArgs = append(reorderedArgs, arg)
|
||||
// if this arg does not contain a "=", then the next index may contain the value for this flag
|
||||
nextIndexMayContainValue = !strings.Contains(arg, "=")
|
||||
|
||||
// simply append any remaining args
|
||||
} else {
|
||||
remainingArgs = append(remainingArgs, arg)
|
||||
}
|
||||
}
|
||||
|
||||
return append(reorderedArgs, remainingArgs...)
|
||||
}
|
||||
|
||||
// argIsFlag checks if an arg is one of our command flags
|
||||
func argIsFlag(commandFlags []Flag, arg string) bool {
|
||||
if arg == "-" || arg == "--" {
|
||||
// `-` is never a flag
|
||||
// `--` is an option-value when following a flag, and a delimiter indicating the end of options in other cases.
|
||||
return false
|
||||
}
|
||||
// flags always start with a -
|
||||
if !strings.HasPrefix(arg, "-") {
|
||||
return false
|
||||
}
|
||||
// this line turns `--flag` into `flag`
|
||||
if strings.HasPrefix(arg, "--") {
|
||||
arg = strings.Replace(arg, "-", "", 2)
|
||||
}
|
||||
// this line turns `-flag` into `flag`
|
||||
if strings.HasPrefix(arg, "-") {
|
||||
arg = strings.Replace(arg, "-", "", 1)
|
||||
}
|
||||
// this line turns `flag=value` into `flag`
|
||||
arg = strings.Split(arg, "=")[0]
|
||||
// look through all the flags, to see if the `arg` is one of our flags
|
||||
for _, flag := range commandFlags {
|
||||
for _, key := range strings.Split(flag.GetName(), ",") {
|
||||
key := strings.TrimSpace(key)
|
||||
if key == arg {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
// return false if this arg was not one of our flags
|
||||
return false
|
||||
}
|
||||
|
||||
// Names returns the names including short names and aliases.
|
||||
func (c Command) Names() []string {
|
||||
names := []string{c.Name}
|
||||
|
||||
if c.ShortName != "" {
|
||||
names = append(names, c.ShortName)
|
||||
}
|
||||
|
||||
return append(names, c.Aliases...)
|
||||
}
|
||||
|
||||
// HasName returns true if Command.Name or Command.ShortName matches given name
|
||||
func (c Command) HasName(name string) bool {
|
||||
for _, n := range c.Names() {
|
||||
if n == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c Command) startApp(ctx *Context) error {
|
||||
app := NewApp()
|
||||
app.Metadata = ctx.App.Metadata
|
||||
app.ExitErrHandler = ctx.App.ExitErrHandler
|
||||
// set the name and usage
|
||||
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
|
||||
if c.HelpName == "" {
|
||||
app.HelpName = c.HelpName
|
||||
} else {
|
||||
app.HelpName = app.Name
|
||||
}
|
||||
|
||||
app.Usage = c.Usage
|
||||
app.Description = c.Description
|
||||
app.ArgsUsage = c.ArgsUsage
|
||||
|
||||
// set CommandNotFound
|
||||
app.CommandNotFound = ctx.App.CommandNotFound
|
||||
app.CustomAppHelpTemplate = c.CustomHelpTemplate
|
||||
|
||||
// set the flags and commands
|
||||
app.Commands = c.Subcommands
|
||||
app.Flags = c.Flags
|
||||
app.HideHelp = c.HideHelp
|
||||
|
||||
app.Version = ctx.App.Version
|
||||
app.HideVersion = ctx.App.HideVersion
|
||||
app.Compiled = ctx.App.Compiled
|
||||
app.Author = ctx.App.Author
|
||||
app.Email = ctx.App.Email
|
||||
app.Writer = ctx.App.Writer
|
||||
app.ErrWriter = ctx.App.ErrWriter
|
||||
app.UseShortOptionHandling = ctx.App.UseShortOptionHandling
|
||||
|
||||
app.categories = CommandCategories{}
|
||||
for _, command := range c.Subcommands {
|
||||
app.categories = app.categories.AddCommand(command.Category, command)
|
||||
}
|
||||
|
||||
sort.Sort(app.categories)
|
||||
|
||||
// bash completion
|
||||
app.EnableBashCompletion = ctx.App.EnableBashCompletion
|
||||
if c.BashComplete != nil {
|
||||
app.BashComplete = c.BashComplete
|
||||
}
|
||||
|
||||
// set the actions
|
||||
app.Before = c.Before
|
||||
app.After = c.After
|
||||
if c.Action != nil {
|
||||
app.Action = c.Action
|
||||
} else {
|
||||
app.Action = helpSubcommand.Action
|
||||
}
|
||||
app.OnUsageError = c.OnUsageError
|
||||
|
||||
for index, cc := range app.Commands {
|
||||
app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
|
||||
}
|
||||
|
||||
return app.RunAsSubcommand(ctx)
|
||||
}
|
||||
|
||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
||||
func (c Command) VisibleFlags() []Flag {
|
||||
return visibleFlags(c.Flags)
|
||||
}
|
||||
-348
@@ -1,348 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Context is a type that is passed through to
|
||||
// each Handler action in a cli application. Context
|
||||
// can be used to retrieve context-specific Args and
|
||||
// parsed command-line options.
|
||||
type Context struct {
|
||||
App *App
|
||||
Command Command
|
||||
shellComplete bool
|
||||
flagSet *flag.FlagSet
|
||||
setFlags map[string]bool
|
||||
parentContext *Context
|
||||
}
|
||||
|
||||
// NewContext creates a new context. For use in when invoking an App or Command action.
|
||||
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
|
||||
c := &Context{App: app, flagSet: set, parentContext: parentCtx}
|
||||
|
||||
if parentCtx != nil {
|
||||
c.shellComplete = parentCtx.shellComplete
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// NumFlags returns the number of flags set
|
||||
func (c *Context) NumFlags() int {
|
||||
return c.flagSet.NFlag()
|
||||
}
|
||||
|
||||
// Set sets a context flag to a value.
|
||||
func (c *Context) Set(name, value string) error {
|
||||
c.setFlags = nil
|
||||
return c.flagSet.Set(name, value)
|
||||
}
|
||||
|
||||
// GlobalSet sets a context flag to a value on the global flagset
|
||||
func (c *Context) GlobalSet(name, value string) error {
|
||||
globalContext(c).setFlags = nil
|
||||
return globalContext(c).flagSet.Set(name, value)
|
||||
}
|
||||
|
||||
// IsSet determines if the flag was actually set
|
||||
func (c *Context) IsSet(name string) bool {
|
||||
if c.setFlags == nil {
|
||||
c.setFlags = make(map[string]bool)
|
||||
|
||||
c.flagSet.Visit(func(f *flag.Flag) {
|
||||
c.setFlags[f.Name] = true
|
||||
})
|
||||
|
||||
c.flagSet.VisitAll(func(f *flag.Flag) {
|
||||
if _, ok := c.setFlags[f.Name]; ok {
|
||||
return
|
||||
}
|
||||
c.setFlags[f.Name] = false
|
||||
})
|
||||
|
||||
// XXX hack to support IsSet for flags with EnvVar
|
||||
//
|
||||
// There isn't an easy way to do this with the current implementation since
|
||||
// whether a flag was set via an environment variable is very difficult to
|
||||
// determine here. Instead, we intend to introduce a backwards incompatible
|
||||
// change in version 2 to add `IsSet` to the Flag interface to push the
|
||||
// responsibility closer to where the information required to determine
|
||||
// whether a flag is set by non-standard means such as environment
|
||||
// variables is available.
|
||||
//
|
||||
// See https://github.com/urfave/cli/issues/294 for additional discussion
|
||||
flags := c.Command.Flags
|
||||
if c.Command.Name == "" { // cannot == Command{} since it contains slice types
|
||||
if c.App != nil {
|
||||
flags = c.App.Flags
|
||||
}
|
||||
}
|
||||
for _, f := range flags {
|
||||
eachName(f.GetName(), func(name string) {
|
||||
if isSet, ok := c.setFlags[name]; isSet || !ok {
|
||||
// Check if a flag is set
|
||||
if isSet {
|
||||
// If the flag is set, also set its other aliases
|
||||
eachName(f.GetName(), func(name string) {
|
||||
c.setFlags[name] = true
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(f)
|
||||
if val.Kind() == reflect.Ptr {
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
filePathValue := val.FieldByName("FilePath")
|
||||
if filePathValue.IsValid() {
|
||||
eachName(filePathValue.String(), func(filePath string) {
|
||||
if _, err := os.Stat(filePath); err == nil {
|
||||
c.setFlags[name] = true
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
envVarValue := val.FieldByName("EnvVar")
|
||||
if envVarValue.IsValid() {
|
||||
eachName(envVarValue.String(), func(envVar string) {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if _, ok := syscall.Getenv(envVar); ok {
|
||||
c.setFlags[name] = true
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return c.setFlags[name]
|
||||
}
|
||||
|
||||
// GlobalIsSet determines if the global flag was actually set
|
||||
func (c *Context) GlobalIsSet(name string) bool {
|
||||
ctx := c
|
||||
if ctx.parentContext != nil {
|
||||
ctx = ctx.parentContext
|
||||
}
|
||||
|
||||
for ; ctx != nil; ctx = ctx.parentContext {
|
||||
if ctx.IsSet(name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FlagNames returns a slice of flag names used in this context.
|
||||
func (c *Context) FlagNames() (names []string) {
|
||||
for _, f := range c.Command.Flags {
|
||||
name := strings.Split(f.GetName(), ",")[0]
|
||||
if name == "help" {
|
||||
continue
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GlobalFlagNames returns a slice of global flag names used by the app.
|
||||
func (c *Context) GlobalFlagNames() (names []string) {
|
||||
for _, f := range c.App.Flags {
|
||||
name := strings.Split(f.GetName(), ",")[0]
|
||||
if name == "help" || name == "version" {
|
||||
continue
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Parent returns the parent context, if any
|
||||
func (c *Context) Parent() *Context {
|
||||
return c.parentContext
|
||||
}
|
||||
|
||||
// value returns the value of the flag coressponding to `name`
|
||||
func (c *Context) value(name string) interface{} {
|
||||
return c.flagSet.Lookup(name).Value.(flag.Getter).Get()
|
||||
}
|
||||
|
||||
// Args contains apps console arguments
|
||||
type Args []string
|
||||
|
||||
// Args returns the command line arguments associated with the context.
|
||||
func (c *Context) Args() Args {
|
||||
args := Args(c.flagSet.Args())
|
||||
return args
|
||||
}
|
||||
|
||||
// NArg returns the number of the command line arguments.
|
||||
func (c *Context) NArg() int {
|
||||
return len(c.Args())
|
||||
}
|
||||
|
||||
// Get returns the nth argument, or else a blank string
|
||||
func (a Args) Get(n int) string {
|
||||
if len(a) > n {
|
||||
return a[n]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// First returns the first argument, or else a blank string
|
||||
func (a Args) First() string {
|
||||
return a.Get(0)
|
||||
}
|
||||
|
||||
// Tail returns the rest of the arguments (not the first one)
|
||||
// or else an empty string slice
|
||||
func (a Args) Tail() []string {
|
||||
if len(a) >= 2 {
|
||||
return []string(a)[1:]
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Present checks if there are any arguments present
|
||||
func (a Args) Present() bool {
|
||||
return len(a) != 0
|
||||
}
|
||||
|
||||
// Swap swaps arguments at the given indexes
|
||||
func (a Args) Swap(from, to int) error {
|
||||
if from >= len(a) || to >= len(a) {
|
||||
return errors.New("index out of range")
|
||||
}
|
||||
a[from], a[to] = a[to], a[from]
|
||||
return nil
|
||||
}
|
||||
|
||||
func globalContext(ctx *Context) *Context {
|
||||
if ctx == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
if ctx.parentContext == nil {
|
||||
return ctx
|
||||
}
|
||||
ctx = ctx.parentContext
|
||||
}
|
||||
}
|
||||
|
||||
func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
|
||||
if ctx.parentContext != nil {
|
||||
ctx = ctx.parentContext
|
||||
}
|
||||
for ; ctx != nil; ctx = ctx.parentContext {
|
||||
if f := ctx.flagSet.Lookup(name); f != nil {
|
||||
return ctx.flagSet
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
|
||||
switch ff.Value.(type) {
|
||||
case *StringSlice:
|
||||
default:
|
||||
_ = set.Set(name, ff.Value.String())
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
|
||||
visited := make(map[string]bool)
|
||||
set.Visit(func(f *flag.Flag) {
|
||||
visited[f.Name] = true
|
||||
})
|
||||
for _, f := range flags {
|
||||
parts := strings.Split(f.GetName(), ",")
|
||||
if len(parts) == 1 {
|
||||
continue
|
||||
}
|
||||
var ff *flag.Flag
|
||||
for _, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
if visited[name] {
|
||||
if ff != nil {
|
||||
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
|
||||
}
|
||||
ff = set.Lookup(name)
|
||||
}
|
||||
}
|
||||
if ff == nil {
|
||||
continue
|
||||
}
|
||||
for _, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
if !visited[name] {
|
||||
copyFlag(name, ff, set)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type requiredFlagsErr interface {
|
||||
error
|
||||
getMissingFlags() []string
|
||||
}
|
||||
|
||||
type errRequiredFlags struct {
|
||||
missingFlags []string
|
||||
}
|
||||
|
||||
func (e *errRequiredFlags) Error() string {
|
||||
numberOfMissingFlags := len(e.missingFlags)
|
||||
if numberOfMissingFlags == 1 {
|
||||
return fmt.Sprintf("Required flag %q not set", e.missingFlags[0])
|
||||
}
|
||||
joinedMissingFlags := strings.Join(e.missingFlags, ", ")
|
||||
return fmt.Sprintf("Required flags %q not set", joinedMissingFlags)
|
||||
}
|
||||
|
||||
func (e *errRequiredFlags) getMissingFlags() []string {
|
||||
return e.missingFlags
|
||||
}
|
||||
|
||||
func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr {
|
||||
var missingFlags []string
|
||||
for _, f := range flags {
|
||||
if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() {
|
||||
var flagPresent bool
|
||||
var flagName string
|
||||
for _, key := range strings.Split(f.GetName(), ",") {
|
||||
key = strings.TrimSpace(key)
|
||||
if len(key) > 1 {
|
||||
flagName = key
|
||||
}
|
||||
|
||||
if context.IsSet(key) {
|
||||
flagPresent = true
|
||||
}
|
||||
}
|
||||
|
||||
if !flagPresent && flagName != "" {
|
||||
missingFlags = append(missingFlags, flagName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingFlags) != 0 {
|
||||
return &errRequiredFlags{missingFlags: missingFlags}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
-151
@@ -1,151 +0,0 @@
|
||||
//go:build !urfave_cli_no_docs
|
||||
// +build !urfave_cli_no_docs
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/cpuguy83/go-md2man/v2/md2man"
|
||||
)
|
||||
|
||||
// ToMarkdown creates a markdown string for the `*App`
|
||||
// The function errors if either parsing or writing of the string fails.
|
||||
func (a *App) ToMarkdown() (string, error) {
|
||||
var w bytes.Buffer
|
||||
if err := a.writeDocTemplate(&w); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return w.String(), nil
|
||||
}
|
||||
|
||||
// ToMan creates a man page string for the `*App`
|
||||
// The function errors if either parsing or writing of the string fails.
|
||||
func (a *App) ToMan() (string, error) {
|
||||
var w bytes.Buffer
|
||||
if err := a.writeDocTemplate(&w); err != nil {
|
||||
return "", err
|
||||
}
|
||||
man := md2man.Render(w.Bytes())
|
||||
return string(man), nil
|
||||
}
|
||||
|
||||
type cliTemplate struct {
|
||||
App *App
|
||||
Commands []string
|
||||
GlobalArgs []string
|
||||
SynopsisArgs []string
|
||||
}
|
||||
|
||||
func (a *App) writeDocTemplate(w io.Writer) error {
|
||||
const name = "cli"
|
||||
t, err := template.New(name).Parse(MarkdownDocTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return t.ExecuteTemplate(w, name, &cliTemplate{
|
||||
App: a,
|
||||
Commands: prepareCommands(a.Commands, 0),
|
||||
GlobalArgs: prepareArgsWithValues(a.Flags),
|
||||
SynopsisArgs: prepareArgsSynopsis(a.Flags),
|
||||
})
|
||||
}
|
||||
|
||||
func prepareCommands(commands []Command, level int) []string {
|
||||
coms := []string{}
|
||||
for i := range commands {
|
||||
command := &commands[i]
|
||||
if command.Hidden {
|
||||
continue
|
||||
}
|
||||
usage := ""
|
||||
if command.Usage != "" {
|
||||
usage = command.Usage
|
||||
}
|
||||
|
||||
prepared := fmt.Sprintf("%s %s\n\n%s\n",
|
||||
strings.Repeat("#", level+2),
|
||||
strings.Join(command.Names(), ", "),
|
||||
usage,
|
||||
)
|
||||
|
||||
flags := prepareArgsWithValues(command.Flags)
|
||||
if len(flags) > 0 {
|
||||
prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n"))
|
||||
}
|
||||
|
||||
coms = append(coms, prepared)
|
||||
|
||||
// recursevly iterate subcommands
|
||||
if len(command.Subcommands) > 0 {
|
||||
coms = append(
|
||||
coms,
|
||||
prepareCommands(command.Subcommands, level+1)...,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return coms
|
||||
}
|
||||
|
||||
func prepareArgsWithValues(flags []Flag) []string {
|
||||
return prepareFlags(flags, ", ", "**", "**", `""`, true)
|
||||
}
|
||||
|
||||
func prepareArgsSynopsis(flags []Flag) []string {
|
||||
return prepareFlags(flags, "|", "[", "]", "[value]", false)
|
||||
}
|
||||
|
||||
func prepareFlags(
|
||||
flags []Flag,
|
||||
sep, opener, closer, value string,
|
||||
addDetails bool,
|
||||
) []string {
|
||||
args := []string{}
|
||||
for _, f := range flags {
|
||||
flag, ok := f.(DocGenerationFlag)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
modifiedArg := opener
|
||||
for _, s := range strings.Split(flag.GetName(), ",") {
|
||||
trimmed := strings.TrimSpace(s)
|
||||
if len(modifiedArg) > len(opener) {
|
||||
modifiedArg += sep
|
||||
}
|
||||
if len(trimmed) > 1 {
|
||||
modifiedArg += fmt.Sprintf("--%s", trimmed)
|
||||
} else {
|
||||
modifiedArg += fmt.Sprintf("-%s", trimmed)
|
||||
}
|
||||
}
|
||||
modifiedArg += closer
|
||||
if flag.TakesValue() {
|
||||
modifiedArg += fmt.Sprintf("=%s", value)
|
||||
}
|
||||
|
||||
if addDetails {
|
||||
modifiedArg += flagDetails(flag)
|
||||
}
|
||||
|
||||
args = append(args, modifiedArg+"\n")
|
||||
|
||||
}
|
||||
sort.Strings(args)
|
||||
return args
|
||||
}
|
||||
|
||||
// flagDetails returns a string containing the flags metadata
|
||||
func flagDetails(flag DocGenerationFlag) string {
|
||||
description := flag.GetUsage()
|
||||
value := flag.GetValue()
|
||||
if value != "" {
|
||||
description += " (default: " + value + ")"
|
||||
}
|
||||
return ": " + description
|
||||
}
|
||||
-115
@@ -1,115 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// OsExiter is the function used when the app exits. If not set defaults to os.Exit.
|
||||
var OsExiter = os.Exit
|
||||
|
||||
// ErrWriter is used to write errors to the user. This can be anything
|
||||
// implementing the io.Writer interface and defaults to os.Stderr.
|
||||
var ErrWriter io.Writer = os.Stderr
|
||||
|
||||
// MultiError is an error that wraps multiple errors.
|
||||
type MultiError struct {
|
||||
Errors []error
|
||||
}
|
||||
|
||||
// NewMultiError creates a new MultiError. Pass in one or more errors.
|
||||
func NewMultiError(err ...error) MultiError {
|
||||
return MultiError{Errors: err}
|
||||
}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (m MultiError) Error() string {
|
||||
errs := make([]string, len(m.Errors))
|
||||
for i, err := range m.Errors {
|
||||
errs[i] = err.Error()
|
||||
}
|
||||
|
||||
return strings.Join(errs, "\n")
|
||||
}
|
||||
|
||||
type ErrorFormatter interface {
|
||||
Format(s fmt.State, verb rune)
|
||||
}
|
||||
|
||||
// ExitCoder is the interface checked by `App` and `Command` for a custom exit
|
||||
// code
|
||||
type ExitCoder interface {
|
||||
error
|
||||
ExitCode() int
|
||||
}
|
||||
|
||||
// ExitError fulfills both the builtin `error` interface and `ExitCoder`
|
||||
type ExitError struct {
|
||||
exitCode int
|
||||
message interface{}
|
||||
}
|
||||
|
||||
// NewExitError makes a new *ExitError
|
||||
func NewExitError(message interface{}, exitCode int) *ExitError {
|
||||
return &ExitError{
|
||||
exitCode: exitCode,
|
||||
message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns the string message, fulfilling the interface required by
|
||||
// `error`
|
||||
func (ee *ExitError) Error() string {
|
||||
return fmt.Sprintf("%v", ee.message)
|
||||
}
|
||||
|
||||
// ExitCode returns the exit code, fulfilling the interface required by
|
||||
// `ExitCoder`
|
||||
func (ee *ExitError) ExitCode() int {
|
||||
return ee.exitCode
|
||||
}
|
||||
|
||||
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
|
||||
// so prints the error to stderr (if it is non-empty) and calls OsExiter with the
|
||||
// given exit code. If the given error is a MultiError, then this func is
|
||||
// called on all members of the Errors slice and calls OsExiter with the last exit code.
|
||||
func HandleExitCoder(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if exitErr, ok := err.(ExitCoder); ok {
|
||||
if err.Error() != "" {
|
||||
if _, ok := exitErr.(ErrorFormatter); ok {
|
||||
fmt.Fprintf(ErrWriter, "%+v\n", err)
|
||||
} else {
|
||||
fmt.Fprintln(ErrWriter, err)
|
||||
}
|
||||
}
|
||||
OsExiter(exitErr.ExitCode())
|
||||
return
|
||||
}
|
||||
|
||||
if multiErr, ok := err.(MultiError); ok {
|
||||
code := handleMultiError(multiErr)
|
||||
OsExiter(code)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func handleMultiError(multiErr MultiError) int {
|
||||
code := 1
|
||||
for _, merr := range multiErr.Errors {
|
||||
if multiErr2, ok := merr.(MultiError); ok {
|
||||
code = handleMultiError(multiErr2)
|
||||
} else {
|
||||
fmt.Fprintln(ErrWriter, merr)
|
||||
if exitErr, ok := merr.(ExitCoder); ok {
|
||||
code = exitErr.ExitCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
-194
@@ -1,194 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// ToFishCompletion creates a fish completion string for the `*App`
|
||||
// The function errors if either parsing or writing of the string fails.
|
||||
func (a *App) ToFishCompletion() (string, error) {
|
||||
var w bytes.Buffer
|
||||
if err := a.writeFishCompletionTemplate(&w); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return w.String(), nil
|
||||
}
|
||||
|
||||
type fishCompletionTemplate struct {
|
||||
App *App
|
||||
Completions []string
|
||||
AllCommands []string
|
||||
}
|
||||
|
||||
func (a *App) writeFishCompletionTemplate(w io.Writer) error {
|
||||
const name = "cli"
|
||||
t, err := template.New(name).Parse(FishCompletionTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allCommands := []string{}
|
||||
|
||||
// Add global flags
|
||||
completions := a.prepareFishFlags(a.VisibleFlags(), allCommands)
|
||||
|
||||
// Add help flag
|
||||
if !a.HideHelp {
|
||||
completions = append(
|
||||
completions,
|
||||
a.prepareFishFlags([]Flag{HelpFlag}, allCommands)...,
|
||||
)
|
||||
}
|
||||
|
||||
// Add version flag
|
||||
if !a.HideVersion {
|
||||
completions = append(
|
||||
completions,
|
||||
a.prepareFishFlags([]Flag{VersionFlag}, allCommands)...,
|
||||
)
|
||||
}
|
||||
|
||||
// Add commands and their flags
|
||||
completions = append(
|
||||
completions,
|
||||
a.prepareFishCommands(a.VisibleCommands(), &allCommands, []string{})...,
|
||||
)
|
||||
|
||||
return t.ExecuteTemplate(w, name, &fishCompletionTemplate{
|
||||
App: a,
|
||||
Completions: completions,
|
||||
AllCommands: allCommands,
|
||||
})
|
||||
}
|
||||
|
||||
func (a *App) prepareFishCommands(commands []Command, allCommands *[]string, previousCommands []string) []string {
|
||||
completions := []string{}
|
||||
for i := range commands {
|
||||
command := &commands[i]
|
||||
|
||||
if command.Hidden {
|
||||
continue
|
||||
}
|
||||
|
||||
var completion strings.Builder
|
||||
completion.WriteString(fmt.Sprintf(
|
||||
"complete -r -c %s -n '%s' -a '%s'",
|
||||
a.Name,
|
||||
a.fishSubcommandHelper(previousCommands),
|
||||
strings.Join(command.Names(), " "),
|
||||
))
|
||||
|
||||
if command.Usage != "" {
|
||||
completion.WriteString(fmt.Sprintf(" -d '%s'",
|
||||
escapeSingleQuotes(command.Usage)))
|
||||
}
|
||||
|
||||
if !command.HideHelp {
|
||||
completions = append(
|
||||
completions,
|
||||
a.prepareFishFlags([]Flag{HelpFlag}, command.Names())...,
|
||||
)
|
||||
}
|
||||
|
||||
*allCommands = append(*allCommands, command.Names()...)
|
||||
completions = append(completions, completion.String())
|
||||
completions = append(
|
||||
completions,
|
||||
a.prepareFishFlags(command.Flags, command.Names())...,
|
||||
)
|
||||
|
||||
// recursevly iterate subcommands
|
||||
if len(command.Subcommands) > 0 {
|
||||
completions = append(
|
||||
completions,
|
||||
a.prepareFishCommands(
|
||||
command.Subcommands, allCommands, command.Names(),
|
||||
)...,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return completions
|
||||
}
|
||||
|
||||
func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string {
|
||||
completions := []string{}
|
||||
for _, f := range flags {
|
||||
flag, ok := f.(DocGenerationFlag)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
completion := &strings.Builder{}
|
||||
completion.WriteString(fmt.Sprintf(
|
||||
"complete -c %s -n '%s'",
|
||||
a.Name,
|
||||
a.fishSubcommandHelper(previousCommands),
|
||||
))
|
||||
|
||||
fishAddFileFlag(f, completion)
|
||||
|
||||
for idx, opt := range strings.Split(flag.GetName(), ",") {
|
||||
if idx == 0 {
|
||||
completion.WriteString(fmt.Sprintf(
|
||||
" -l %s", strings.TrimSpace(opt),
|
||||
))
|
||||
} else {
|
||||
completion.WriteString(fmt.Sprintf(
|
||||
" -s %s", strings.TrimSpace(opt),
|
||||
))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if flag.TakesValue() {
|
||||
completion.WriteString(" -r")
|
||||
}
|
||||
|
||||
if flag.GetUsage() != "" {
|
||||
completion.WriteString(fmt.Sprintf(" -d '%s'",
|
||||
escapeSingleQuotes(flag.GetUsage())))
|
||||
}
|
||||
|
||||
completions = append(completions, completion.String())
|
||||
}
|
||||
|
||||
return completions
|
||||
}
|
||||
|
||||
func fishAddFileFlag(flag Flag, completion *strings.Builder) {
|
||||
switch f := flag.(type) {
|
||||
case GenericFlag:
|
||||
if f.TakesFile {
|
||||
return
|
||||
}
|
||||
case StringFlag:
|
||||
if f.TakesFile {
|
||||
return
|
||||
}
|
||||
case StringSliceFlag:
|
||||
if f.TakesFile {
|
||||
return
|
||||
}
|
||||
}
|
||||
completion.WriteString(" -f")
|
||||
}
|
||||
|
||||
func (a *App) fishSubcommandHelper(allCommands []string) string {
|
||||
fishHelper := fmt.Sprintf("__fish_%s_no_subcommand", a.Name)
|
||||
if len(allCommands) > 0 {
|
||||
fishHelper = fmt.Sprintf(
|
||||
"__fish_seen_subcommand_from %s",
|
||||
strings.Join(allCommands, " "),
|
||||
)
|
||||
}
|
||||
return fishHelper
|
||||
|
||||
}
|
||||
|
||||
func escapeSingleQuotes(input string) string {
|
||||
return strings.Replace(input, `'`, `\'`, -1)
|
||||
}
|
||||
-348
@@ -1,348 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const defaultPlaceholder = "value"
|
||||
|
||||
// BashCompletionFlag enables bash-completion for all commands and subcommands
|
||||
var BashCompletionFlag Flag = BoolFlag{
|
||||
Name: "generate-bash-completion",
|
||||
Hidden: true,
|
||||
}
|
||||
|
||||
// VersionFlag prints the version for the application
|
||||
var VersionFlag Flag = BoolFlag{
|
||||
Name: "version, v",
|
||||
Usage: "print the version",
|
||||
}
|
||||
|
||||
// HelpFlag prints the help for all commands and subcommands
|
||||
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
|
||||
// unless HideHelp is set to true)
|
||||
var HelpFlag Flag = BoolFlag{
|
||||
Name: "help, h",
|
||||
Usage: "show help",
|
||||
}
|
||||
|
||||
// FlagStringer converts a flag definition to a string. This is used by help
|
||||
// to display a flag.
|
||||
var FlagStringer FlagStringFunc = stringifyFlag
|
||||
|
||||
// FlagNamePrefixer converts a full flag name and its placeholder into the help
|
||||
// message flag prefix. This is used by the default FlagStringer.
|
||||
var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames
|
||||
|
||||
// FlagEnvHinter annotates flag help message with the environment variable
|
||||
// details. This is used by the default FlagStringer.
|
||||
var FlagEnvHinter FlagEnvHintFunc = withEnvHint
|
||||
|
||||
// FlagFileHinter annotates flag help message with the environment variable
|
||||
// details. This is used by the default FlagStringer.
|
||||
var FlagFileHinter FlagFileHintFunc = withFileHint
|
||||
|
||||
// FlagsByName is a slice of Flag.
|
||||
type FlagsByName []Flag
|
||||
|
||||
func (f FlagsByName) Len() int {
|
||||
return len(f)
|
||||
}
|
||||
|
||||
func (f FlagsByName) Less(i, j int) bool {
|
||||
return lexicographicLess(f[i].GetName(), f[j].GetName())
|
||||
}
|
||||
|
||||
func (f FlagsByName) Swap(i, j int) {
|
||||
f[i], f[j] = f[j], f[i]
|
||||
}
|
||||
|
||||
// Flag is a common interface related to parsing flags in cli.
|
||||
// For more advanced flag parsing techniques, it is recommended that
|
||||
// this interface be implemented.
|
||||
type Flag interface {
|
||||
fmt.Stringer
|
||||
// Apply Flag settings to the given flag set
|
||||
Apply(*flag.FlagSet)
|
||||
GetName() string
|
||||
}
|
||||
|
||||
// RequiredFlag is an interface that allows us to mark flags as required
|
||||
// it allows flags required flags to be backwards compatible with the Flag interface
|
||||
type RequiredFlag interface {
|
||||
Flag
|
||||
|
||||
IsRequired() bool
|
||||
}
|
||||
|
||||
// DocGenerationFlag is an interface that allows documentation generation for the flag
|
||||
type DocGenerationFlag interface {
|
||||
Flag
|
||||
|
||||
// TakesValue returns true if the flag takes a value, otherwise false
|
||||
TakesValue() bool
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
GetUsage() string
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
GetValue() string
|
||||
}
|
||||
|
||||
// errorableFlag is an interface that allows us to return errors during apply
|
||||
// it allows flags defined in this library to return errors in a fashion backwards compatible
|
||||
// TODO remove in v2 and modify the existing Flag interface to return errors
|
||||
type errorableFlag interface {
|
||||
Flag
|
||||
|
||||
ApplyWithError(*flag.FlagSet) error
|
||||
}
|
||||
|
||||
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
|
||||
set := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||
|
||||
for _, f := range flags {
|
||||
//TODO remove in v2 when errorableFlag is removed
|
||||
if ef, ok := f.(errorableFlag); ok {
|
||||
if err := ef.ApplyWithError(set); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
f.Apply(set)
|
||||
}
|
||||
}
|
||||
set.SetOutput(ioutil.Discard)
|
||||
return set, nil
|
||||
}
|
||||
|
||||
func eachName(longName string, fn func(string)) {
|
||||
parts := strings.Split(longName, ",")
|
||||
for _, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
fn(name)
|
||||
}
|
||||
}
|
||||
|
||||
func visibleFlags(fl []Flag) []Flag {
|
||||
var visible []Flag
|
||||
for _, f := range fl {
|
||||
field := flagValue(f).FieldByName("Hidden")
|
||||
if !field.IsValid() || !field.Bool() {
|
||||
visible = append(visible, f)
|
||||
}
|
||||
}
|
||||
return visible
|
||||
}
|
||||
|
||||
func prefixFor(name string) (prefix string) {
|
||||
if len(name) == 1 {
|
||||
prefix = "-"
|
||||
} else {
|
||||
prefix = "--"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Returns the placeholder, if any, and the unquoted usage string.
|
||||
func unquoteUsage(usage string) (string, string) {
|
||||
for i := 0; i < len(usage); i++ {
|
||||
if usage[i] == '`' {
|
||||
for j := i + 1; j < len(usage); j++ {
|
||||
if usage[j] == '`' {
|
||||
name := usage[i+1 : j]
|
||||
usage = usage[:i] + name + usage[j+1:]
|
||||
return name, usage
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return "", usage
|
||||
}
|
||||
|
||||
func prefixedNames(fullName, placeholder string) string {
|
||||
var prefixed string
|
||||
parts := strings.Split(fullName, ",")
|
||||
for i, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
prefixed += prefixFor(name) + name
|
||||
if placeholder != "" {
|
||||
prefixed += " " + placeholder
|
||||
}
|
||||
if i < len(parts)-1 {
|
||||
prefixed += ", "
|
||||
}
|
||||
}
|
||||
return prefixed
|
||||
}
|
||||
|
||||
func withEnvHint(envVar, str string) string {
|
||||
envText := ""
|
||||
if envVar != "" {
|
||||
prefix := "$"
|
||||
suffix := ""
|
||||
sep := ", $"
|
||||
if runtime.GOOS == "windows" {
|
||||
prefix = "%"
|
||||
suffix = "%"
|
||||
sep = "%, %"
|
||||
}
|
||||
envText = " [" + prefix + strings.Join(strings.Split(envVar, ","), sep) + suffix + "]"
|
||||
}
|
||||
return str + envText
|
||||
}
|
||||
|
||||
func withFileHint(filePath, str string) string {
|
||||
fileText := ""
|
||||
if filePath != "" {
|
||||
fileText = fmt.Sprintf(" [%s]", filePath)
|
||||
}
|
||||
return str + fileText
|
||||
}
|
||||
|
||||
func flagValue(f Flag) reflect.Value {
|
||||
fv := reflect.ValueOf(f)
|
||||
for fv.Kind() == reflect.Ptr {
|
||||
fv = reflect.Indirect(fv)
|
||||
}
|
||||
return fv
|
||||
}
|
||||
|
||||
func stringifyFlag(f Flag) string {
|
||||
fv := flagValue(f)
|
||||
|
||||
switch f.(type) {
|
||||
case IntSliceFlag:
|
||||
return FlagFileHinter(
|
||||
fv.FieldByName("FilePath").String(),
|
||||
FlagEnvHinter(
|
||||
fv.FieldByName("EnvVar").String(),
|
||||
stringifyIntSliceFlag(f.(IntSliceFlag)),
|
||||
),
|
||||
)
|
||||
case Int64SliceFlag:
|
||||
return FlagFileHinter(
|
||||
fv.FieldByName("FilePath").String(),
|
||||
FlagEnvHinter(
|
||||
fv.FieldByName("EnvVar").String(),
|
||||
stringifyInt64SliceFlag(f.(Int64SliceFlag)),
|
||||
),
|
||||
)
|
||||
case StringSliceFlag:
|
||||
return FlagFileHinter(
|
||||
fv.FieldByName("FilePath").String(),
|
||||
FlagEnvHinter(
|
||||
fv.FieldByName("EnvVar").String(),
|
||||
stringifyStringSliceFlag(f.(StringSliceFlag)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String())
|
||||
|
||||
needsPlaceholder := false
|
||||
defaultValueString := ""
|
||||
|
||||
if val := fv.FieldByName("Value"); val.IsValid() {
|
||||
needsPlaceholder = true
|
||||
defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface())
|
||||
|
||||
if val.Kind() == reflect.String && val.String() != "" {
|
||||
defaultValueString = fmt.Sprintf(" (default: %q)", val.String())
|
||||
}
|
||||
}
|
||||
|
||||
if defaultValueString == " (default: )" {
|
||||
defaultValueString = ""
|
||||
}
|
||||
|
||||
if needsPlaceholder && placeholder == "" {
|
||||
placeholder = defaultPlaceholder
|
||||
}
|
||||
|
||||
usageWithDefault := strings.TrimSpace(usage + defaultValueString)
|
||||
|
||||
return FlagFileHinter(
|
||||
fv.FieldByName("FilePath").String(),
|
||||
FlagEnvHinter(
|
||||
fv.FieldByName("EnvVar").String(),
|
||||
FlagNamePrefixer(fv.FieldByName("Name").String(), placeholder)+"\t"+usageWithDefault,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func stringifyIntSliceFlag(f IntSliceFlag) string {
|
||||
var defaultVals []string
|
||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
||||
for _, i := range f.Value.Value() {
|
||||
defaultVals = append(defaultVals, strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
||||
}
|
||||
|
||||
func stringifyInt64SliceFlag(f Int64SliceFlag) string {
|
||||
var defaultVals []string
|
||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
||||
for _, i := range f.Value.Value() {
|
||||
defaultVals = append(defaultVals, strconv.FormatInt(i, 10))
|
||||
}
|
||||
}
|
||||
|
||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
||||
}
|
||||
|
||||
func stringifyStringSliceFlag(f StringSliceFlag) string {
|
||||
var defaultVals []string
|
||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
||||
for _, s := range f.Value.Value() {
|
||||
if len(s) > 0 {
|
||||
defaultVals = append(defaultVals, strconv.Quote(s))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
||||
}
|
||||
|
||||
func stringifySliceFlag(usage, name string, defaultVals []string) string {
|
||||
placeholder, usage := unquoteUsage(usage)
|
||||
if placeholder == "" {
|
||||
placeholder = defaultPlaceholder
|
||||
}
|
||||
|
||||
defaultVal := ""
|
||||
if len(defaultVals) > 0 {
|
||||
defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", "))
|
||||
}
|
||||
|
||||
usageWithDefault := strings.TrimSpace(usage + defaultVal)
|
||||
return FlagNamePrefixer(name, placeholder) + "\t" + usageWithDefault
|
||||
}
|
||||
|
||||
func flagFromFileEnv(filePath, envName string) (val string, ok bool) {
|
||||
for _, envVar := range strings.Split(envName, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
return envVal, true
|
||||
}
|
||||
}
|
||||
for _, fileVar := range strings.Split(filePath, ",") {
|
||||
if fileVar != "" {
|
||||
if data, err := ioutil.ReadFile(fileVar); err == nil {
|
||||
return string(data), true
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// BoolFlag is a flag with type bool
|
||||
type BoolFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Required bool
|
||||
Hidden bool
|
||||
Destination *bool
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
// (for usage defaults)
|
||||
func (f BoolFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag
|
||||
func (f BoolFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// IsRequired returns whether or not the flag is required
|
||||
func (f BoolFlag) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false
|
||||
func (f BoolFlag) TakesValue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (f BoolFlag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (f BoolFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Bool looks up the value of a local BoolFlag, returns
|
||||
// false if not found
|
||||
func (c *Context) Bool(name string) bool {
|
||||
return lookupBool(name, c.flagSet)
|
||||
}
|
||||
|
||||
// GlobalBool looks up the value of a global BoolFlag, returns
|
||||
// false if not found
|
||||
func (c *Context) GlobalBool(name string) bool {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupBool(name, fs)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f BoolFlag) Apply(set *flag.FlagSet) {
|
||||
_ = f.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
val := false
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
if envVal == "" {
|
||||
val = false
|
||||
} else {
|
||||
envValBool, err := strconv.ParseBool(envVal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
val = envValBool
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.BoolVar(f.Destination, name, val, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Bool(name, val, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupBool(name string, set *flag.FlagSet) bool {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
parsed, err := strconv.ParseBool(f.Value.String())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
return false
|
||||
}
|
||||
-110
@@ -1,110 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// BoolTFlag is a flag with type bool that is true by default
|
||||
type BoolTFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Required bool
|
||||
Hidden bool
|
||||
Destination *bool
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
// (for usage defaults)
|
||||
func (f BoolTFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag
|
||||
func (f BoolTFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// IsRequired returns whether or not the flag is required
|
||||
func (f BoolTFlag) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false
|
||||
func (f BoolTFlag) TakesValue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (f BoolTFlag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (f BoolTFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// BoolT looks up the value of a local BoolTFlag, returns
|
||||
// false if not found
|
||||
func (c *Context) BoolT(name string) bool {
|
||||
return lookupBoolT(name, c.flagSet)
|
||||
}
|
||||
|
||||
// GlobalBoolT looks up the value of a global BoolTFlag, returns
|
||||
// false if not found
|
||||
func (c *Context) GlobalBoolT(name string) bool {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupBoolT(name, fs)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f BoolTFlag) Apply(set *flag.FlagSet) {
|
||||
_ = f.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
val := true
|
||||
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
if envVal == "" {
|
||||
val = false
|
||||
} else {
|
||||
envValBool, err := strconv.ParseBool(envVal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
val = envValBool
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.BoolVar(f.Destination, name, val, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Bool(name, val, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupBoolT(name string, set *flag.FlagSet) bool {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
parsed, err := strconv.ParseBool(f.Value.String())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
return false
|
||||
}
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
|
||||
type DurationFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Required bool
|
||||
Hidden bool
|
||||
Value time.Duration
|
||||
Destination *time.Duration
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
// (for usage defaults)
|
||||
func (f DurationFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag
|
||||
func (f DurationFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// IsRequired returns whether or not the flag is required
|
||||
func (f DurationFlag) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false
|
||||
func (f DurationFlag) TakesValue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (f DurationFlag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (f DurationFlag) GetValue() string {
|
||||
return f.Value.String()
|
||||
}
|
||||
|
||||
// Duration looks up the value of a local DurationFlag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) Duration(name string) time.Duration {
|
||||
return lookupDuration(name, c.flagSet)
|
||||
}
|
||||
|
||||
// GlobalDuration looks up the value of a global DurationFlag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) GlobalDuration(name string) time.Duration {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupDuration(name, fs)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f DurationFlag) Apply(set *flag.FlagSet) {
|
||||
_ = f.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
envValDuration, err := time.ParseDuration(envVal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = envValDuration
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.DurationVar(f.Destination, name, f.Value, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Duration(name, f.Value, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
parsed, err := time.ParseDuration(f.Value.String())
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
return 0
|
||||
}
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Float64Flag is a flag with type float64
|
||||
type Float64Flag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Required bool
|
||||
Hidden bool
|
||||
Value float64
|
||||
Destination *float64
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
// (for usage defaults)
|
||||
func (f Float64Flag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag
|
||||
func (f Float64Flag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// IsRequired returns whether or not the flag is required
|
||||
func (f Float64Flag) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false
|
||||
func (f Float64Flag) TakesValue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (f Float64Flag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (f Float64Flag) GetValue() string {
|
||||
return fmt.Sprintf("%f", f.Value)
|
||||
}
|
||||
|
||||
// Float64 looks up the value of a local Float64Flag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) Float64(name string) float64 {
|
||||
return lookupFloat64(name, c.flagSet)
|
||||
}
|
||||
|
||||
// GlobalFloat64 looks up the value of a global Float64Flag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) GlobalFloat64(name string) float64 {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupFloat64(name, fs)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f Float64Flag) Apply(set *flag.FlagSet) {
|
||||
_ = f.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
envValFloat, err := strconv.ParseFloat(envVal, 10)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = envValFloat
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.Float64Var(f.Destination, name, f.Value, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Float64(name, f.Value, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupFloat64(name string, set *flag.FlagSet) float64 {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
parsed, err := strconv.ParseFloat(f.Value.String(), 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
return 0
|
||||
}
|
||||
-110
@@ -1,110 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Generic is a generic parseable type identified by a specific flag
|
||||
type Generic interface {
|
||||
Set(value string) error
|
||||
String() string
|
||||
}
|
||||
|
||||
// GenericFlag is a flag with type Generic
|
||||
type GenericFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Required bool
|
||||
Hidden bool
|
||||
TakesFile bool
|
||||
Value Generic
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
// (for usage defaults)
|
||||
func (f GenericFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag
|
||||
func (f GenericFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// IsRequired returns whether or not the flag is required
|
||||
func (f GenericFlag) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false
|
||||
func (f GenericFlag) TakesValue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (f GenericFlag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (f GenericFlag) GetValue() string {
|
||||
if f.Value != nil {
|
||||
return f.Value.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Apply takes the flagset and calls Set on the generic flag with the value
|
||||
// provided by the user for parsing by the flag
|
||||
// Ignores parsing errors
|
||||
func (f GenericFlag) Apply(set *flag.FlagSet) {
|
||||
_ = f.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// ApplyWithError takes the flagset and calls Set on the generic flag with the value
|
||||
// provided by the user for parsing by the flag
|
||||
func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
val := f.Value
|
||||
if fileEnvVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
if err := val.Set(fileEnvVal); err != nil {
|
||||
return fmt.Errorf("could not parse %s as value for flag %s: %s", fileEnvVal, f.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generic looks up the value of a local GenericFlag, returns
|
||||
// nil if not found
|
||||
func (c *Context) Generic(name string) interface{} {
|
||||
return lookupGeneric(name, c.flagSet)
|
||||
}
|
||||
|
||||
// GlobalGeneric looks up the value of a global GenericFlag, returns
|
||||
// nil if not found
|
||||
func (c *Context) GlobalGeneric(name string) interface{} {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupGeneric(name, fs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
parsed, err := f.Value, error(nil)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
-105
@@ -1,105 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// IntFlag is a flag with type int
|
||||
type IntFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Required bool
|
||||
Hidden bool
|
||||
Value int
|
||||
Destination *int
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
// (for usage defaults)
|
||||
func (f IntFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag
|
||||
func (f IntFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// IsRequired returns whether or not the flag is required
|
||||
func (f IntFlag) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false
|
||||
func (f IntFlag) TakesValue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (f IntFlag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (f IntFlag) GetValue() string {
|
||||
return fmt.Sprintf("%d", f.Value)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f IntFlag) Apply(set *flag.FlagSet) {
|
||||
_ = f.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f IntFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
f.Value = int(envValInt)
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.IntVar(f.Destination, name, f.Value, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Int(name, f.Value, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Int looks up the value of a local IntFlag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) Int(name string) int {
|
||||
return lookupInt(name, c.flagSet)
|
||||
}
|
||||
|
||||
// GlobalInt looks up the value of a global IntFlag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) GlobalInt(name string) int {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupInt(name, fs)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func lookupInt(name string, set *flag.FlagSet) int {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return int(parsed)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Int64Flag is a flag with type int64
|
||||
type Int64Flag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Required bool
|
||||
Hidden bool
|
||||
Value int64
|
||||
Destination *int64
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
// (for usage defaults)
|
||||
func (f Int64Flag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag
|
||||
func (f Int64Flag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// IsRequired returns whether or not the flag is required
|
||||
func (f Int64Flag) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false
|
||||
func (f Int64Flag) TakesValue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (f Int64Flag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (f Int64Flag) GetValue() string {
|
||||
return fmt.Sprintf("%d", f.Value)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f Int64Flag) Apply(set *flag.FlagSet) {
|
||||
_ = f.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = envValInt
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.Int64Var(f.Destination, name, f.Value, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Int64(name, f.Value, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Int64 looks up the value of a local Int64Flag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) Int64(name string) int64 {
|
||||
return lookupInt64(name, c.flagSet)
|
||||
}
|
||||
|
||||
// GlobalInt64 looks up the value of a global Int64Flag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) GlobalInt64(name string) int64 {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupInt64(name, fs)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func lookupInt64(name string, set *flag.FlagSet) int64 {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
return 0
|
||||
}
|
||||
-199
@@ -1,199 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter
|
||||
type Int64Slice []int64
|
||||
|
||||
// Set parses the value into an integer and appends it to the list of values
|
||||
func (f *Int64Slice) Set(value string) error {
|
||||
tmp, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*f = append(*f, tmp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f *Int64Slice) String() string {
|
||||
slice := make([]string, len(*f))
|
||||
for i, v := range *f {
|
||||
slice[i] = strconv.FormatInt(v, 10)
|
||||
}
|
||||
|
||||
return strings.Join(slice, ",")
|
||||
}
|
||||
|
||||
// Value returns the slice of ints set by this flag
|
||||
func (f *Int64Slice) Value() []int64 {
|
||||
return *f
|
||||
}
|
||||
|
||||
// Get returns the slice of ints set by this flag
|
||||
func (f *Int64Slice) Get() interface{} {
|
||||
return *f
|
||||
}
|
||||
|
||||
// Int64SliceFlag is a flag with type *Int64Slice
|
||||
type Int64SliceFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Required bool
|
||||
Hidden bool
|
||||
Value *Int64Slice
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
// (for usage defaults)
|
||||
func (f Int64SliceFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag
|
||||
func (f Int64SliceFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// IsRequired returns whether or not the flag is required
|
||||
func (f Int64SliceFlag) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false
|
||||
func (f Int64SliceFlag) TakesValue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (f Int64SliceFlag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (f Int64SliceFlag) GetValue() string {
|
||||
if f.Value != nil {
|
||||
return f.Value.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
|
||||
_ = f.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
newVal := &Int64Slice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
if err := newVal.Set(s); err != nil {
|
||||
return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
}
|
||||
if f.Value == nil {
|
||||
f.Value = newVal
|
||||
} else {
|
||||
*f.Value = *newVal
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Value == nil {
|
||||
f.Value = &Int64Slice{}
|
||||
}
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Int64Slice looks up the value of a local Int64SliceFlag, returns
|
||||
// nil if not found
|
||||
func (c *Context) Int64Slice(name string) []int64 {
|
||||
return lookupInt64Slice(name, c.flagSet)
|
||||
}
|
||||
|
||||
// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns
|
||||
// nil if not found
|
||||
func (c *Context) GlobalInt64Slice(name string) []int64 {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupInt64Slice(name, fs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
value, ok := f.Value.(*Int64Slice)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// extract the slice from asserted value
|
||||
parsed := value.Value()
|
||||
|
||||
// extract default value from the flag
|
||||
var defaultVal []int64
|
||||
for _, v := range strings.Split(f.DefValue, ",") {
|
||||
if v != "" {
|
||||
int64Value, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defaultVal = append(defaultVal, int64Value)
|
||||
}
|
||||
}
|
||||
// if the current value is not equal to the default value
|
||||
// remove the default values from the flag
|
||||
if !isInt64SliceEqual(parsed, defaultVal) {
|
||||
for _, v := range defaultVal {
|
||||
parsed = removeFromInt64Slice(parsed, v)
|
||||
}
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeFromInt64Slice(slice []int64, val int64) []int64 {
|
||||
for i, v := range slice {
|
||||
if v == val {
|
||||
ret := append([]int64{}, slice[:i]...)
|
||||
ret = append(ret, slice[i+1:]...)
|
||||
return ret
|
||||
}
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func isInt64SliceEqual(newValue, defaultValue []int64) bool {
|
||||
// If one is nil, the other must also be nil.
|
||||
if (newValue == nil) != (defaultValue == nil) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(newValue) != len(defaultValue) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, v := range newValue {
|
||||
if v != defaultValue[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
-198
@@ -1,198 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter
|
||||
type IntSlice []int
|
||||
|
||||
// Set parses the value into an integer and appends it to the list of values
|
||||
func (f *IntSlice) Set(value string) error {
|
||||
tmp, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*f = append(*f, tmp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f *IntSlice) String() string {
|
||||
slice := make([]string, len(*f))
|
||||
for i, v := range *f {
|
||||
slice[i] = strconv.Itoa(v)
|
||||
}
|
||||
|
||||
return strings.Join(slice, ",")
|
||||
}
|
||||
|
||||
// Value returns the slice of ints set by this flag
|
||||
func (f *IntSlice) Value() []int {
|
||||
return *f
|
||||
}
|
||||
|
||||
// Get returns the slice of ints set by this flag
|
||||
func (f *IntSlice) Get() interface{} {
|
||||
return *f
|
||||
}
|
||||
|
||||
// IntSliceFlag is a flag with type *IntSlice
|
||||
type IntSliceFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Required bool
|
||||
Hidden bool
|
||||
Value *IntSlice
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
// (for usage defaults)
|
||||
func (f IntSliceFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag
|
||||
func (f IntSliceFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// IsRequired returns whether or not the flag is required
|
||||
func (f IntSliceFlag) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false
|
||||
func (f IntSliceFlag) TakesValue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (f IntSliceFlag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (f IntSliceFlag) GetValue() string {
|
||||
if f.Value != nil {
|
||||
return f.Value.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
|
||||
_ = f.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
newVal := &IntSlice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
if err := newVal.Set(s); err != nil {
|
||||
return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
}
|
||||
if f.Value == nil {
|
||||
f.Value = newVal
|
||||
} else {
|
||||
*f.Value = *newVal
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Value == nil {
|
||||
f.Value = &IntSlice{}
|
||||
}
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IntSlice looks up the value of a local IntSliceFlag, returns
|
||||
// nil if not found
|
||||
func (c *Context) IntSlice(name string) []int {
|
||||
return lookupIntSlice(name, c.flagSet)
|
||||
}
|
||||
|
||||
// GlobalIntSlice looks up the value of a global IntSliceFlag, returns
|
||||
// nil if not found
|
||||
func (c *Context) GlobalIntSlice(name string) []int {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupIntSlice(name, fs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupIntSlice(name string, set *flag.FlagSet) []int {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
value, ok := f.Value.(*IntSlice)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
// extract the slice from asserted value
|
||||
slice := value.Value()
|
||||
|
||||
// extract default value from the flag
|
||||
var defaultVal []int
|
||||
for _, v := range strings.Split(f.DefValue, ",") {
|
||||
if v != "" {
|
||||
intValue, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defaultVal = append(defaultVal, intValue)
|
||||
}
|
||||
}
|
||||
// if the current value is not equal to the default value
|
||||
// remove the default values from the flag
|
||||
if !isIntSliceEqual(slice, defaultVal) {
|
||||
for _, v := range defaultVal {
|
||||
slice = removeFromIntSlice(slice, v)
|
||||
}
|
||||
}
|
||||
return slice
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeFromIntSlice(slice []int, val int) []int {
|
||||
for i, v := range slice {
|
||||
if v == val {
|
||||
ret := append([]int{}, slice[:i]...)
|
||||
ret = append(ret, slice[i+1:]...)
|
||||
return ret
|
||||
}
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func isIntSliceEqual(newValue, defaultValue []int) bool {
|
||||
// If one is nil, the other must also be nil.
|
||||
if (newValue == nil) != (defaultValue == nil) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(newValue) != len(defaultValue) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, v := range newValue {
|
||||
if v != defaultValue[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
package cli
|
||||
|
||||
import "flag"
|
||||
|
||||
// StringFlag is a flag with type string
|
||||
type StringFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Required bool
|
||||
Hidden bool
|
||||
TakesFile bool
|
||||
Value string
|
||||
Destination *string
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
// (for usage defaults)
|
||||
func (f StringFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag
|
||||
func (f StringFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// IsRequired returns whether or not the flag is required
|
||||
func (f StringFlag) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false
|
||||
func (f StringFlag) TakesValue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (f StringFlag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (f StringFlag) GetValue() string {
|
||||
return f.Value
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f StringFlag) Apply(set *flag.FlagSet) {
|
||||
_ = f.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f StringFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
f.Value = envVal
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.StringVar(f.Destination, name, f.Value, f.Usage)
|
||||
return
|
||||
}
|
||||
set.String(name, f.Value, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String looks up the value of a local StringFlag, returns
|
||||
// "" if not found
|
||||
func (c *Context) String(name string) string {
|
||||
return lookupString(name, c.flagSet)
|
||||
}
|
||||
|
||||
// GlobalString looks up the value of a global StringFlag, returns
|
||||
// "" if not found
|
||||
func (c *Context) GlobalString(name string) string {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupString(name, fs)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func lookupString(name string, set *flag.FlagSet) string {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
parsed, err := f.Value.String(), error(nil)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
return ""
|
||||
}
|
||||
-184
@@ -1,184 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter
|
||||
type StringSlice []string
|
||||
|
||||
// Set appends the string value to the list of values
|
||||
func (f *StringSlice) Set(value string) error {
|
||||
*f = append(*f, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f *StringSlice) String() string {
|
||||
return strings.Join(*f, ",")
|
||||
}
|
||||
|
||||
// Value returns the slice of strings set by this flag
|
||||
func (f *StringSlice) Value() []string {
|
||||
return *f
|
||||
}
|
||||
|
||||
// Get returns the slice of strings set by this flag
|
||||
func (f *StringSlice) Get() interface{} {
|
||||
return *f
|
||||
}
|
||||
|
||||
// StringSliceFlag is a flag with type *StringSlice
|
||||
type StringSliceFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Required bool
|
||||
Hidden bool
|
||||
TakesFile bool
|
||||
Value *StringSlice
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
// (for usage defaults)
|
||||
func (f StringSliceFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag
|
||||
func (f StringSliceFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// IsRequired returns whether or not the flag is required
|
||||
func (f StringSliceFlag) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false
|
||||
func (f StringSliceFlag) TakesValue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (f StringSliceFlag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (f StringSliceFlag) GetValue() string {
|
||||
if f.Value != nil {
|
||||
return f.Value.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
|
||||
_ = f.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
newVal := &StringSlice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
if err := newVal.Set(s); err != nil {
|
||||
return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
}
|
||||
if f.Value == nil {
|
||||
f.Value = newVal
|
||||
} else {
|
||||
*f.Value = *newVal
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Value == nil {
|
||||
f.Value = &StringSlice{}
|
||||
}
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StringSlice looks up the value of a local StringSliceFlag, returns
|
||||
// nil if not found
|
||||
func (c *Context) StringSlice(name string) []string {
|
||||
return lookupStringSlice(name, c.flagSet)
|
||||
}
|
||||
|
||||
// GlobalStringSlice looks up the value of a global StringSliceFlag, returns
|
||||
// nil if not found
|
||||
func (c *Context) GlobalStringSlice(name string) []string {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupStringSlice(name, fs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupStringSlice(name string, set *flag.FlagSet) []string {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
value, ok := f.Value.(*StringSlice)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
// extract the slice from asserted value
|
||||
slice := value.Value()
|
||||
|
||||
// extract default value from the flag
|
||||
var defaultVal []string
|
||||
for _, v := range strings.Split(f.DefValue, ",") {
|
||||
defaultVal = append(defaultVal, v)
|
||||
}
|
||||
|
||||
// if the current value is not equal to the default value
|
||||
// remove the default values from the flag
|
||||
if !isStringSliceEqual(slice, defaultVal) {
|
||||
for _, v := range defaultVal {
|
||||
slice = removeFromStringSlice(slice, v)
|
||||
}
|
||||
}
|
||||
return slice
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeFromStringSlice(slice []string, val string) []string {
|
||||
for i, v := range slice {
|
||||
if v == val {
|
||||
ret := append([]string{}, slice[:i]...)
|
||||
ret = append(ret, slice[i+1:]...)
|
||||
return ret
|
||||
}
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func isStringSliceEqual(newValue, defaultValue []string) bool {
|
||||
// If one is nil, the other must also be nil.
|
||||
if (newValue == nil) != (defaultValue == nil) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(newValue) != len(defaultValue) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, v := range newValue {
|
||||
if v != defaultValue[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// UintFlag is a flag with type uint
|
||||
type UintFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Required bool
|
||||
Hidden bool
|
||||
Value uint
|
||||
Destination *uint
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
// (for usage defaults)
|
||||
func (f UintFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag
|
||||
func (f UintFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// IsRequired returns whether or not the flag is required
|
||||
func (f UintFlag) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false
|
||||
func (f UintFlag) TakesValue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (f UintFlag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f UintFlag) Apply(set *flag.FlagSet) {
|
||||
_ = f.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f UintFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
envValInt, err := strconv.ParseUint(envVal, 0, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = uint(envValInt)
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.UintVar(f.Destination, name, f.Value, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Uint(name, f.Value, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (f UintFlag) GetValue() string {
|
||||
return fmt.Sprintf("%d", f.Value)
|
||||
}
|
||||
|
||||
// Uint looks up the value of a local UintFlag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) Uint(name string) uint {
|
||||
return lookupUint(name, c.flagSet)
|
||||
}
|
||||
|
||||
// GlobalUint looks up the value of a global UintFlag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) GlobalUint(name string) uint {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupUint(name, fs)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func lookupUint(name string, set *flag.FlagSet) uint {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return uint(parsed)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Uint64Flag is a flag with type uint64
|
||||
type Uint64Flag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Required bool
|
||||
Hidden bool
|
||||
Value uint64
|
||||
Destination *uint64
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
// (for usage defaults)
|
||||
func (f Uint64Flag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag
|
||||
func (f Uint64Flag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// IsRequired returns whether or not the flag is required
|
||||
func (f Uint64Flag) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false
|
||||
func (f Uint64Flag) TakesValue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (f Uint64Flag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (f Uint64Flag) GetValue() string {
|
||||
return fmt.Sprintf("%d", f.Value)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f Uint64Flag) Apply(set *flag.FlagSet) {
|
||||
_ = f.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
envValInt, err := strconv.ParseUint(envVal, 0, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = envValInt
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.Uint64Var(f.Destination, name, f.Value, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Uint64(name, f.Value, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Uint64 looks up the value of a local Uint64Flag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) Uint64(name string) uint64 {
|
||||
return lookupUint64(name, c.flagSet)
|
||||
}
|
||||
|
||||
// GlobalUint64 looks up the value of a global Uint64Flag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) GlobalUint64(name string) uint64 {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupUint64(name, fs)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func lookupUint64(name string, set *flag.FlagSet) uint64 {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
return 0
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
package cli
|
||||
|
||||
// BashCompleteFunc is an action to execute when the bash-completion flag is set
|
||||
type BashCompleteFunc func(*Context)
|
||||
|
||||
// BeforeFunc is an action to execute before any subcommands are run, but after
|
||||
// the context is ready if a non-nil error is returned, no subcommands are run
|
||||
type BeforeFunc func(*Context) error
|
||||
|
||||
// AfterFunc is an action to execute after any subcommands are run, but after the
|
||||
// subcommand has finished it is run even if Action() panics
|
||||
type AfterFunc func(*Context) error
|
||||
|
||||
// ActionFunc is the action to execute when no subcommands are specified
|
||||
type ActionFunc func(*Context) error
|
||||
|
||||
// CommandNotFoundFunc is executed if the proper command cannot be found
|
||||
type CommandNotFoundFunc func(*Context, string)
|
||||
|
||||
// OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying
|
||||
// customized usage error messages. This function is able to replace the
|
||||
// original error messages. If this function is not set, the "Incorrect usage"
|
||||
// is displayed and the execution is interrupted.
|
||||
type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error
|
||||
|
||||
// ExitErrHandlerFunc is executed if provided in order to handle ExitError values
|
||||
// returned by Actions and Before/After functions.
|
||||
type ExitErrHandlerFunc func(context *Context, err error)
|
||||
|
||||
// FlagStringFunc is used by the help generation to display a flag, which is
|
||||
// expected to be a single line.
|
||||
type FlagStringFunc func(Flag) string
|
||||
|
||||
// FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix
|
||||
// text for a flag's full name.
|
||||
type FlagNamePrefixFunc func(fullName, placeholder string) string
|
||||
|
||||
// FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help
|
||||
// with the environment variable details.
|
||||
type FlagEnvHintFunc func(envVar, str string) string
|
||||
|
||||
// FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help
|
||||
// with the file path details.
|
||||
type FlagFileHintFunc func(filePath, str string) string
|
||||
-363
@@ -1,363 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var helpCommand = Command{
|
||||
Name: "help",
|
||||
Aliases: []string{"h"},
|
||||
Usage: "Shows a list of commands or help for one command",
|
||||
ArgsUsage: "[command]",
|
||||
Action: func(c *Context) error {
|
||||
args := c.Args()
|
||||
if args.Present() {
|
||||
return ShowCommandHelp(c, args.First())
|
||||
}
|
||||
|
||||
_ = ShowAppHelp(c)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var helpSubcommand = Command{
|
||||
Name: "help",
|
||||
Aliases: []string{"h"},
|
||||
Usage: "Shows a list of commands or help for one command",
|
||||
ArgsUsage: "[command]",
|
||||
Action: func(c *Context) error {
|
||||
args := c.Args()
|
||||
if args.Present() {
|
||||
return ShowCommandHelp(c, args.First())
|
||||
}
|
||||
|
||||
return ShowSubcommandHelp(c)
|
||||
},
|
||||
}
|
||||
|
||||
// Prints help for the App or Command
|
||||
type helpPrinter func(w io.Writer, templ string, data interface{})
|
||||
|
||||
// Prints help for the App or Command with custom template function.
|
||||
type helpPrinterCustom func(w io.Writer, templ string, data interface{}, customFunc map[string]interface{})
|
||||
|
||||
// HelpPrinter is a function that writes the help output. If not set explicitly,
|
||||
// this calls HelpPrinterCustom using only the default template functions.
|
||||
//
|
||||
// If custom logic for printing help is required, this function can be
|
||||
// overridden. If the ExtraInfo field is defined on an App, this function
|
||||
// should not be modified, as HelpPrinterCustom will be used directly in order
|
||||
// to capture the extra information.
|
||||
var HelpPrinter helpPrinter = printHelp
|
||||
|
||||
// HelpPrinterCustom is a function that writes the help output. It is used as
|
||||
// the default implementation of HelpPrinter, and may be called directly if
|
||||
// the ExtraInfo field is set on an App.
|
||||
var HelpPrinterCustom helpPrinterCustom = printHelpCustom
|
||||
|
||||
// VersionPrinter prints the version for the App
|
||||
var VersionPrinter = printVersion
|
||||
|
||||
// ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code.
|
||||
func ShowAppHelpAndExit(c *Context, exitCode int) {
|
||||
_ = ShowAppHelp(c)
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
// ShowAppHelp is an action that displays the help.
|
||||
func ShowAppHelp(c *Context) error {
|
||||
template := c.App.CustomAppHelpTemplate
|
||||
if template == "" {
|
||||
template = AppHelpTemplate
|
||||
}
|
||||
|
||||
if c.App.ExtraInfo == nil {
|
||||
HelpPrinter(c.App.Writer, template, c.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
customAppData := func() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"ExtraInfo": c.App.ExtraInfo,
|
||||
}
|
||||
}
|
||||
HelpPrinterCustom(c.App.Writer, template, c.App, customAppData())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultAppComplete prints the list of subcommands as the default app completion method
|
||||
func DefaultAppComplete(c *Context) {
|
||||
DefaultCompleteWithFlags(nil)(c)
|
||||
}
|
||||
|
||||
func printCommandSuggestions(commands []Command, writer io.Writer) {
|
||||
for _, command := range commands {
|
||||
if command.Hidden {
|
||||
continue
|
||||
}
|
||||
if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" {
|
||||
for _, name := range command.Names() {
|
||||
_, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage)
|
||||
}
|
||||
} else {
|
||||
for _, name := range command.Names() {
|
||||
_, _ = fmt.Fprintf(writer, "%s\n", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cliArgContains(flagName string) bool {
|
||||
for _, name := range strings.Split(flagName, ",") {
|
||||
name = strings.TrimSpace(name)
|
||||
count := utf8.RuneCountInString(name)
|
||||
if count > 2 {
|
||||
count = 2
|
||||
}
|
||||
flag := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
|
||||
for _, a := range os.Args {
|
||||
if a == flag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) {
|
||||
cur := strings.TrimPrefix(lastArg, "-")
|
||||
cur = strings.TrimPrefix(cur, "-")
|
||||
for _, flag := range flags {
|
||||
if bflag, ok := flag.(BoolFlag); ok && bflag.Hidden {
|
||||
continue
|
||||
}
|
||||
for _, name := range strings.Split(flag.GetName(), ",") {
|
||||
name = strings.TrimSpace(name)
|
||||
// this will get total count utf8 letters in flag name
|
||||
count := utf8.RuneCountInString(name)
|
||||
if count > 2 {
|
||||
count = 2 // resuse this count to generate single - or -- in flag completion
|
||||
}
|
||||
// if flag name has more than one utf8 letter and last argument in cli has -- prefix then
|
||||
// skip flag completion for short flags example -v or -x
|
||||
if strings.HasPrefix(lastArg, "--") && count == 1 {
|
||||
continue
|
||||
}
|
||||
// match if last argument matches this flag and it is not repeated
|
||||
if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(flag.GetName()) {
|
||||
flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
|
||||
_, _ = fmt.Fprintln(writer, flagCompletion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultCompleteWithFlags(cmd *Command) func(c *Context) {
|
||||
return func(c *Context) {
|
||||
if len(os.Args) > 2 {
|
||||
lastArg := os.Args[len(os.Args)-2]
|
||||
if strings.HasPrefix(lastArg, "-") {
|
||||
printFlagSuggestions(lastArg, c.App.Flags, c.App.Writer)
|
||||
if cmd != nil {
|
||||
printFlagSuggestions(lastArg, cmd.Flags, c.App.Writer)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
if cmd != nil {
|
||||
printCommandSuggestions(cmd.Subcommands, c.App.Writer)
|
||||
} else {
|
||||
printCommandSuggestions(c.App.Commands, c.App.Writer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ShowCommandHelpAndExit - exits with code after showing help
|
||||
func ShowCommandHelpAndExit(c *Context, command string, code int) {
|
||||
_ = ShowCommandHelp(c, command)
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// ShowCommandHelp prints help for the given command
|
||||
func ShowCommandHelp(ctx *Context, command string) error {
|
||||
// show the subcommand help for a command with subcommands
|
||||
if command == "" {
|
||||
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, c := range ctx.App.Commands {
|
||||
if c.HasName(command) {
|
||||
templ := c.CustomHelpTemplate
|
||||
if templ == "" {
|
||||
templ = CommandHelpTemplate
|
||||
}
|
||||
|
||||
HelpPrinter(ctx.App.Writer, templ, c)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.App.CommandNotFound == nil {
|
||||
return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3)
|
||||
}
|
||||
|
||||
ctx.App.CommandNotFound(ctx, command)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShowSubcommandHelp prints help for the given subcommand
|
||||
func ShowSubcommandHelp(c *Context) error {
|
||||
return ShowCommandHelp(c, c.Command.Name)
|
||||
}
|
||||
|
||||
// ShowVersion prints the version number of the App
|
||||
func ShowVersion(c *Context) {
|
||||
VersionPrinter(c)
|
||||
}
|
||||
|
||||
func printVersion(c *Context) {
|
||||
_, _ = fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
|
||||
}
|
||||
|
||||
// ShowCompletions prints the lists of commands within a given context
|
||||
func ShowCompletions(c *Context) {
|
||||
a := c.App
|
||||
if a != nil && a.BashComplete != nil {
|
||||
a.BashComplete(c)
|
||||
}
|
||||
}
|
||||
|
||||
// ShowCommandCompletions prints the custom completions for a given command
|
||||
func ShowCommandCompletions(ctx *Context, command string) {
|
||||
c := ctx.App.Command(command)
|
||||
if c != nil {
|
||||
if c.BashComplete != nil {
|
||||
c.BashComplete(ctx)
|
||||
} else {
|
||||
DefaultCompleteWithFlags(c)(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// printHelpCustom is the default implementation of HelpPrinterCustom.
|
||||
//
|
||||
// The customFuncs map will be combined with a default template.FuncMap to
|
||||
// allow using arbitrary functions in template rendering.
|
||||
func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs map[string]interface{}) {
|
||||
funcMap := template.FuncMap{
|
||||
"join": strings.Join,
|
||||
}
|
||||
for key, value := range customFuncs {
|
||||
funcMap[key] = value
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
|
||||
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
|
||||
err := t.Execute(w, data)
|
||||
if err != nil {
|
||||
// If the writer is closed, t.Execute will fail, and there's nothing
|
||||
// we can do to recover.
|
||||
if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
|
||||
_, _ = fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
_ = w.Flush()
|
||||
}
|
||||
|
||||
func printHelp(out io.Writer, templ string, data interface{}) {
|
||||
HelpPrinterCustom(out, templ, data, nil)
|
||||
}
|
||||
|
||||
func checkVersion(c *Context) bool {
|
||||
found := false
|
||||
if VersionFlag.GetName() != "" {
|
||||
eachName(VersionFlag.GetName(), func(name string) {
|
||||
if c.GlobalBool(name) || c.Bool(name) {
|
||||
found = true
|
||||
}
|
||||
})
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
func checkHelp(c *Context) bool {
|
||||
found := false
|
||||
if HelpFlag.GetName() != "" {
|
||||
eachName(HelpFlag.GetName(), func(name string) {
|
||||
if c.GlobalBool(name) || c.Bool(name) {
|
||||
found = true
|
||||
}
|
||||
})
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
func checkCommandHelp(c *Context, name string) bool {
|
||||
if c.Bool("h") || c.Bool("help") {
|
||||
_ = ShowCommandHelp(c, name)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkSubcommandHelp(c *Context) bool {
|
||||
if c.Bool("h") || c.Bool("help") {
|
||||
_ = ShowSubcommandHelp(c)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) {
|
||||
if !a.EnableBashCompletion {
|
||||
return false, arguments
|
||||
}
|
||||
|
||||
pos := len(arguments) - 1
|
||||
lastArg := arguments[pos]
|
||||
|
||||
if lastArg != "--"+BashCompletionFlag.GetName() {
|
||||
return false, arguments
|
||||
}
|
||||
|
||||
return true, arguments[:pos]
|
||||
}
|
||||
|
||||
func checkCompletions(c *Context) bool {
|
||||
if !c.shellComplete {
|
||||
return false
|
||||
}
|
||||
|
||||
if args := c.Args(); args.Present() {
|
||||
name := args.First()
|
||||
if cmd := c.App.Command(name); cmd != nil {
|
||||
// let the command handle the completion
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
ShowCompletions(c)
|
||||
return true
|
||||
}
|
||||
|
||||
func checkCommandCompletions(c *Context, name string) bool {
|
||||
if !c.shellComplete {
|
||||
return false
|
||||
}
|
||||
|
||||
ShowCommandCompletions(c, name)
|
||||
return true
|
||||
}
|
||||
-94
@@ -1,94 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type iterativeParser interface {
|
||||
newFlagSet() (*flag.FlagSet, error)
|
||||
useShortOptionHandling() bool
|
||||
}
|
||||
|
||||
// To enable short-option handling (e.g., "-it" vs "-i -t") we have to
|
||||
// iteratively catch parsing errors. This way we achieve LR parsing without
|
||||
// transforming any arguments. Otherwise, there is no way we can discriminate
|
||||
// combined short options from common arguments that should be left untouched.
|
||||
// Pass `shellComplete` to continue parsing options on failure during shell
|
||||
// completion when, the user-supplied options may be incomplete.
|
||||
func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComplete bool) error {
|
||||
for {
|
||||
err := set.Parse(args)
|
||||
if !ip.useShortOptionHandling() || err == nil {
|
||||
if shellComplete {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
errStr := err.Error()
|
||||
trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: -")
|
||||
if errStr == trimmed {
|
||||
return err
|
||||
}
|
||||
|
||||
// regenerate the initial args with the split short opts
|
||||
argsWereSplit := false
|
||||
for i, arg := range args {
|
||||
// skip args that are not part of the error message
|
||||
if name := strings.TrimLeft(arg, "-"); name != trimmed {
|
||||
continue
|
||||
}
|
||||
|
||||
// if we can't split, the error was accurate
|
||||
shortOpts := splitShortOptions(set, arg)
|
||||
if len(shortOpts) == 1 {
|
||||
return err
|
||||
}
|
||||
|
||||
// swap current argument with the split version
|
||||
args = append(args[:i], append(shortOpts, args[i+1:]...)...)
|
||||
argsWereSplit = true
|
||||
break
|
||||
}
|
||||
|
||||
// This should be an impossible to reach code path, but in case the arg
|
||||
// splitting failed to happen, this will prevent infinite loops
|
||||
if !argsWereSplit {
|
||||
return err
|
||||
}
|
||||
|
||||
// Since custom parsing failed, replace the flag set before retrying
|
||||
newSet, err := ip.newFlagSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*set = *newSet
|
||||
}
|
||||
}
|
||||
|
||||
func splitShortOptions(set *flag.FlagSet, arg string) []string {
|
||||
shortFlagsExist := func(s string) bool {
|
||||
for _, c := range s[1:] {
|
||||
if f := set.Lookup(string(c)); f == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if !isSplittable(arg) || !shortFlagsExist(arg) {
|
||||
return []string{arg}
|
||||
}
|
||||
|
||||
separated := make([]string, 0, len(arg)-1)
|
||||
for _, flagChar := range arg[1:] {
|
||||
separated = append(separated, "-"+string(flagChar))
|
||||
}
|
||||
|
||||
return separated
|
||||
}
|
||||
|
||||
func isSplittable(flagArg string) bool {
|
||||
return strings.HasPrefix(flagArg, "-") && !strings.HasPrefix(flagArg, "--") && len(flagArg) > 2
|
||||
}
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
package cli
|
||||
|
||||
// AppHelpTemplate is the text template for the Default help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var AppHelpTemplate = `NAME:
|
||||
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
|
||||
|
||||
USAGE:
|
||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
|
||||
|
||||
VERSION:
|
||||
{{.Version}}{{end}}{{end}}{{if .Description}}
|
||||
|
||||
DESCRIPTION:
|
||||
{{.Description}}{{end}}{{if len .Authors}}
|
||||
|
||||
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
|
||||
{{range $index, $author := .Authors}}{{if $index}}
|
||||
{{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
|
||||
|
||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
||||
|
||||
{{.Name}}:{{range .VisibleCommands}}
|
||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
|
||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
{{range $index, $option := .VisibleFlags}}{{if $index}}
|
||||
{{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
|
||||
|
||||
COPYRIGHT:
|
||||
{{.Copyright}}{{end}}
|
||||
`
|
||||
|
||||
// CommandHelpTemplate is the text template for the command help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var CommandHelpTemplate = `NAME:
|
||||
{{.HelpName}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
|
||||
|
||||
CATEGORY:
|
||||
{{.Category}}{{end}}{{if .Description}}
|
||||
|
||||
DESCRIPTION:
|
||||
{{.Description}}{{end}}{{if .VisibleFlags}}
|
||||
|
||||
OPTIONS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
`
|
||||
|
||||
// SubcommandHelpTemplate is the text template for the subcommand help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var SubcommandHelpTemplate = `NAME:
|
||||
{{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}}
|
||||
|
||||
USAGE:
|
||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
|
||||
|
||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
||||
|
||||
{{.Name}}:{{range .VisibleCommands}}
|
||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
|
||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
|
||||
|
||||
OPTIONS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
`
|
||||
|
||||
var MarkdownDocTemplate = `% {{ .App.Name }}(8) {{ .App.Description }}
|
||||
|
||||
% {{ .App.Author }}
|
||||
|
||||
# NAME
|
||||
|
||||
{{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }}
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
{{ .App.Name }}
|
||||
{{ if .SynopsisArgs }}
|
||||
` + "```" + `
|
||||
{{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + `
|
||||
{{ end }}{{ if .App.UsageText }}
|
||||
# DESCRIPTION
|
||||
|
||||
{{ .App.UsageText }}
|
||||
{{ end }}
|
||||
**Usage**:
|
||||
|
||||
` + "```" + `
|
||||
{{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
||||
` + "```" + `
|
||||
{{ if .GlobalArgs }}
|
||||
# GLOBAL OPTIONS
|
||||
{{ range $v := .GlobalArgs }}
|
||||
{{ $v }}{{ end }}
|
||||
{{ end }}{{ if .Commands }}
|
||||
# COMMANDS
|
||||
{{ range $v := .Commands }}
|
||||
{{ $v }}{{ end }}{{ end }}`
|
||||
|
||||
var FishCompletionTemplate = `# {{ .App.Name }} fish shell completion
|
||||
|
||||
function __fish_{{ .App.Name }}_no_subcommand --description 'Test if there has been any subcommand yet'
|
||||
for i in (commandline -opc)
|
||||
if contains -- $i{{ range $v := .AllCommands }} {{ $v }}{{ end }}
|
||||
return 1
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
{{ range $v := .Completions }}{{ $v }}
|
||||
{{ end }}`
|
||||
+7
-6
@@ -1,10 +1,11 @@
|
||||
*.coverprofile
|
||||
coverage.txt
|
||||
node_modules/
|
||||
vendor
|
||||
*.exe
|
||||
*.orig
|
||||
.*envrc
|
||||
.envrc
|
||||
.idea
|
||||
/.local/
|
||||
/internal/
|
||||
/site/
|
||||
package.json
|
||||
package-lock.json
|
||||
coverage.txt
|
||||
examples/*/built-example
|
||||
vendor
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
version: "2"
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- makezero
|
||||
- misspell
|
||||
exclusions:
|
||||
presets:
|
||||
- std-error-handling
|
||||
vendor/github.com/urfave/cli/CODE_OF_CONDUCT.md → vendor/github.com/urfave/cli/v3/CODE_OF_CONDUCT.md
Generated
Vendored
+6
-5
@@ -55,11 +55,12 @@ further defined and clarified by project maintainers.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be
|
||||
reviewed and investigated and will result in a response that is deemed necessary
|
||||
and appropriate to the circumstances. The project team is obligated to maintain
|
||||
confidentiality with regard to the reporter of an incident. Further details of
|
||||
specific enforcement policies may be posted separately.
|
||||
reported by contacting urfave-governance@googlegroups.com, a members-only group
|
||||
that is world-postable. All complaints will be reviewed and investigated and
|
||||
will result in a response that is deemed necessary and appropriate to the
|
||||
circumstances. The project team is obligated to maintain confidentiality with
|
||||
regard to the reporter of an incident. Further details of specific enforcement
|
||||
policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Jeremy Saenz & Contributors
|
||||
Copyright (c) 2023 urfave/cli maintainers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
# NOTE: this Makefile is meant to provide a simplified entry point for humans to
|
||||
# run all of the critical steps to verify one's changes are harmonious in
|
||||
# nature. Keeping target bodies to one line each and abstaining from make magic
|
||||
# are very important so that maintainers and contributors can focus their
|
||||
# attention on files that are primarily Go.
|
||||
|
||||
GO_RUN_BUILD := go run scripts/build.go
|
||||
|
||||
.PHONY: all
|
||||
all: generate vet test check-binary-size gfmrun
|
||||
|
||||
# NOTE: this is a special catch-all rule to run any of the commands
|
||||
# defined in scripts/build.go with optional arguments passed
|
||||
# via GFLAGS (global flags) and FLAGS (command-specific flags), e.g.:
|
||||
#
|
||||
# $ make test GFLAGS='--packages cli'
|
||||
%:
|
||||
$(GO_RUN_BUILD) $(GFLAGS) $* $(FLAGS)
|
||||
|
||||
.PHONY: docs
|
||||
docs:
|
||||
mkdocs build
|
||||
|
||||
.PHONY: serve-docs
|
||||
serve-docs:
|
||||
mkdocs serve
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
# Welcome to urfave/cli
|
||||
|
||||
[![Go Reference][goreference_badge]][goreference_link]
|
||||
[![Go Report Card][goreportcard_badge]][goreportcard_link]
|
||||
[![codecov][codecov_badge]][codecov_link]
|
||||
[![Tests status][test_badge]][test_link]
|
||||
|
||||
urfave/cli is a **declarative**, simple, fast, and fun package for building
|
||||
command line tools in Go featuring:
|
||||
|
||||
- commands and subcommands with alias and prefix match support
|
||||
- flexible and permissive help system
|
||||
- dynamic shell completion for `bash`, `zsh`, `fish`, and `powershell`
|
||||
- no dependencies except Go standard library
|
||||
- input flags for simple types, slices of simple types, time, duration, and
|
||||
others
|
||||
- compound short flag support (`-a` `-b` `-c` can be shortened to `-abc`)
|
||||
- documentation generation in `man` and Markdown (supported via the
|
||||
[`urfave/cli-docs`][urfave/cli-docs] module)
|
||||
- input lookup from:
|
||||
- environment variables
|
||||
- plain text files
|
||||
- structured file formats (supported via the
|
||||
[`urfave/cli-altsrc`][urfave/cli-altsrc] module)
|
||||
|
||||
## Documentation
|
||||
|
||||
See the hosted documentation website at <https://cli.urfave.org>. Contents of
|
||||
this website are built from the [`./docs`](./docs) directory.
|
||||
|
||||
## Support
|
||||
|
||||
Check the [Q&A discussions]. If you don't find answer to your question, [create
|
||||
a new discussion].
|
||||
|
||||
If you found a bug or have a feature request, [create a new issue].
|
||||
|
||||
Please keep in mind that this project is run by unpaid volunteers.
|
||||
|
||||
### License
|
||||
|
||||
See [`LICENSE`](./LICENSE).
|
||||
|
||||
[test_badge]: https://github.com/urfave/cli/actions/workflows/test.yml/badge.svg
|
||||
[test_link]: https://github.com/urfave/cli/actions/workflows/test.yml
|
||||
[goreference_badge]: https://pkg.go.dev/badge/github.com/urfave/cli/v3.svg
|
||||
[goreference_link]: https://pkg.go.dev/github.com/urfave/cli/v3
|
||||
[goreportcard_badge]: https://goreportcard.com/badge/github.com/urfave/cli/v3
|
||||
[goreportcard_link]: https://goreportcard.com/report/github.com/urfave/cli/v3
|
||||
[codecov_badge]: https://codecov.io/gh/urfave/cli/branch/main/graph/badge.svg?token=t9YGWLh05g
|
||||
[codecov_link]: https://codecov.io/gh/urfave/cli
|
||||
[Q&A discussions]: https://github.com/urfave/cli/discussions/categories/q-a
|
||||
[create a new discussion]: https://github.com/urfave/cli/discussions/new?category=q-a
|
||||
[urfave/cli-docs]: https://github.com/urfave/cli-docs
|
||||
[urfave/cli-altsrc]: https://github.com/urfave/cli-altsrc
|
||||
[create a new issue]: https://github.com/urfave/cli/issues/new/choose
|
||||
+400
@@ -0,0 +1,400 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Args interface {
|
||||
// Get returns the nth argument, or else a blank string
|
||||
Get(n int) string
|
||||
// First returns the first argument, or else a blank string
|
||||
First() string
|
||||
// Tail returns the rest of the arguments (not the first one)
|
||||
// or else an empty string slice
|
||||
Tail() []string
|
||||
// Len returns the length of the wrapped slice
|
||||
Len() int
|
||||
// Present checks if there are any arguments present
|
||||
Present() bool
|
||||
// Slice returns a copy of the internal slice
|
||||
Slice() []string
|
||||
}
|
||||
|
||||
type stringSliceArgs struct {
|
||||
v []string
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) Get(n int) string {
|
||||
if len(a.v) > n {
|
||||
return a.v[n]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) First() string {
|
||||
return a.Get(0)
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) Tail() []string {
|
||||
if a.Len() >= 2 {
|
||||
tail := a.v[1:]
|
||||
ret := make([]string, len(tail))
|
||||
copy(ret, tail)
|
||||
return ret
|
||||
}
|
||||
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) Len() int {
|
||||
return len(a.v)
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) Present() bool {
|
||||
return a.Len() != 0
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) Slice() []string {
|
||||
ret := make([]string, len(a.v))
|
||||
copy(ret, a.v)
|
||||
return ret
|
||||
}
|
||||
|
||||
// Argument captures a positional argument that can
|
||||
// be parsed
|
||||
type Argument interface {
|
||||
// which this argument can be accessed using the given name
|
||||
HasName(string) bool
|
||||
|
||||
// Parse the given args and return unparsed args and/or error
|
||||
Parse([]string) ([]string, error)
|
||||
|
||||
// The usage template for this argument to use in help
|
||||
Usage() string
|
||||
|
||||
// The Value of this Arg
|
||||
Get() any
|
||||
}
|
||||
|
||||
// AnyArguments to differentiate between no arguments(nil) vs aleast one
|
||||
var AnyArguments = []Argument{
|
||||
&StringArgs{
|
||||
Max: -1,
|
||||
},
|
||||
}
|
||||
|
||||
type ArgumentBase[T any, C any, VC ValueCreator[T, C]] struct {
|
||||
Name string `json:"name"` // the name of this argument
|
||||
Value T `json:"value"` // the default value of this argument
|
||||
Destination *T `json:"-"` // the destination point for this argument
|
||||
UsageText string `json:"usageText"` // the usage text to show
|
||||
Config C `json:"config"` // config for this argument similar to Flag Config
|
||||
|
||||
value *T
|
||||
}
|
||||
|
||||
func (a *ArgumentBase[T, C, VC]) HasName(s string) bool {
|
||||
return s == a.Name
|
||||
}
|
||||
|
||||
func (a *ArgumentBase[T, C, VC]) Usage() string {
|
||||
if a.UsageText != "" {
|
||||
return a.UsageText
|
||||
}
|
||||
|
||||
usageFormat := "%[1]s"
|
||||
return fmt.Sprintf(usageFormat, a.Name)
|
||||
}
|
||||
|
||||
func (a *ArgumentBase[T, C, VC]) Parse(s []string) ([]string, error) {
|
||||
tracef("calling arg%[1] parse with args %[2]", a.Name, s)
|
||||
|
||||
var vc VC
|
||||
var t T
|
||||
value := vc.Create(a.Value, &t, a.Config)
|
||||
a.value = &t
|
||||
|
||||
tracef("attempting arg%[1] parse", &a.Name)
|
||||
if len(s) > 0 {
|
||||
if err := value.Set(s[0]); err != nil {
|
||||
return s, fmt.Errorf("invalid value %q for argument %s: %v", s[0], a.Name, err)
|
||||
}
|
||||
*a.value = value.Get().(T)
|
||||
tracef("set arg%[1] one value", a.Name, *a.value)
|
||||
}
|
||||
|
||||
if a.Destination != nil {
|
||||
tracef("setting destination")
|
||||
*a.Destination = *a.value
|
||||
}
|
||||
|
||||
if len(s) > 0 {
|
||||
return s[1:], nil
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (a *ArgumentBase[T, C, VC]) Get() any {
|
||||
if a.value != nil {
|
||||
return *a.value
|
||||
}
|
||||
return a.Value
|
||||
}
|
||||
|
||||
// ArgumentsBase is a base type for slice arguments
|
||||
type ArgumentsBase[T any, C any, VC ValueCreator[T, C]] struct {
|
||||
Name string `json:"name"` // the name of this argument
|
||||
Value T `json:"value"` // the default value of this argument
|
||||
Destination *[]T `json:"-"` // the destination point for this argument
|
||||
UsageText string `json:"usageText"` // the usage text to show
|
||||
Min int `json:"minTimes"` // the min num of occurrences of this argument
|
||||
Max int `json:"maxTimes"` // the max num of occurrences of this argument, set to -1 for unlimited
|
||||
Config C `json:"config"` // config for this argument similar to Flag Config
|
||||
|
||||
values []T
|
||||
}
|
||||
|
||||
func (a *ArgumentsBase[T, C, VC]) HasName(s string) bool {
|
||||
return s == a.Name
|
||||
}
|
||||
|
||||
func (a *ArgumentsBase[T, C, VC]) Usage() string {
|
||||
if a.UsageText != "" {
|
||||
return a.UsageText
|
||||
}
|
||||
|
||||
usageFormat := ""
|
||||
if a.Min == 0 {
|
||||
if a.Max == 1 {
|
||||
usageFormat = "[%[1]s]"
|
||||
} else {
|
||||
usageFormat = "[%[1]s ...]"
|
||||
}
|
||||
} else {
|
||||
usageFormat = "%[1]s [%[1]s ...]"
|
||||
}
|
||||
return fmt.Sprintf(usageFormat, a.Name)
|
||||
}
|
||||
|
||||
func (a *ArgumentsBase[T, C, VC]) Parse(s []string) ([]string, error) {
|
||||
tracef("calling arg%[1] parse with args %[2]", &a.Name, s)
|
||||
if a.Max == 0 {
|
||||
return s, fmt.Errorf("args %s has max 0, not parsing argument", a.Name)
|
||||
}
|
||||
if a.Max != -1 && a.Min > a.Max {
|
||||
return s, fmt.Errorf("args %s has min[%d] > max[%d], not parsing argument", a.Name, a.Min, a.Max)
|
||||
}
|
||||
|
||||
count := 0
|
||||
var vc VC
|
||||
var t T
|
||||
value := vc.Create(a.Value, &t, a.Config)
|
||||
a.values = []T{}
|
||||
|
||||
tracef("attempting arg%[1] parse", &a.Name)
|
||||
for _, arg := range s {
|
||||
if err := value.Set(arg); err != nil {
|
||||
return s, fmt.Errorf("invalid value %q for argument %s: %v", arg, a.Name, err)
|
||||
}
|
||||
tracef("set arg%[1] one value", &a.Name, value.Get().(T))
|
||||
a.values = append(a.values, value.Get().(T))
|
||||
count++
|
||||
if count >= a.Max && a.Max > -1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if count < a.Min {
|
||||
return s, fmt.Errorf("sufficient count of arg %s not provided, given %d expected %d", a.Name, count, a.Min)
|
||||
}
|
||||
|
||||
if a.Destination != nil {
|
||||
tracef("appending destination")
|
||||
*a.Destination = a.values // append(*a.Destination, a.values...)
|
||||
}
|
||||
|
||||
return s[count:], nil
|
||||
}
|
||||
|
||||
func (a *ArgumentsBase[T, C, VC]) Get() any {
|
||||
if a.values != nil {
|
||||
return a.values
|
||||
}
|
||||
return []T{}
|
||||
}
|
||||
|
||||
type (
|
||||
FloatArg = ArgumentBase[float64, NoConfig, floatValue[float64]]
|
||||
Float32Arg = ArgumentBase[float32, NoConfig, floatValue[float32]]
|
||||
Float64Arg = ArgumentBase[float64, NoConfig, floatValue[float64]]
|
||||
IntArg = ArgumentBase[int, IntegerConfig, intValue[int]]
|
||||
Int8Arg = ArgumentBase[int8, IntegerConfig, intValue[int8]]
|
||||
Int16Arg = ArgumentBase[int16, IntegerConfig, intValue[int16]]
|
||||
Int32Arg = ArgumentBase[int32, IntegerConfig, intValue[int32]]
|
||||
Int64Arg = ArgumentBase[int64, IntegerConfig, intValue[int64]]
|
||||
StringArg = ArgumentBase[string, StringConfig, stringValue]
|
||||
StringMapArgs = ArgumentBase[map[string]string, StringConfig, StringMap]
|
||||
TimestampArg = ArgumentBase[time.Time, TimestampConfig, timestampValue]
|
||||
UintArg = ArgumentBase[uint, IntegerConfig, uintValue[uint]]
|
||||
Uint8Arg = ArgumentBase[uint8, IntegerConfig, uintValue[uint8]]
|
||||
Uint16Arg = ArgumentBase[uint16, IntegerConfig, uintValue[uint16]]
|
||||
Uint32Arg = ArgumentBase[uint32, IntegerConfig, uintValue[uint32]]
|
||||
Uint64Arg = ArgumentBase[uint64, IntegerConfig, uintValue[uint64]]
|
||||
|
||||
FloatArgs = ArgumentsBase[float64, NoConfig, floatValue[float64]]
|
||||
Float32Args = ArgumentsBase[float32, NoConfig, floatValue[float32]]
|
||||
Float64Args = ArgumentsBase[float64, NoConfig, floatValue[float64]]
|
||||
IntArgs = ArgumentsBase[int, IntegerConfig, intValue[int]]
|
||||
Int8Args = ArgumentsBase[int8, IntegerConfig, intValue[int8]]
|
||||
Int16Args = ArgumentsBase[int16, IntegerConfig, intValue[int16]]
|
||||
Int32Args = ArgumentsBase[int32, IntegerConfig, intValue[int32]]
|
||||
Int64Args = ArgumentsBase[int64, IntegerConfig, intValue[int64]]
|
||||
StringArgs = ArgumentsBase[string, StringConfig, stringValue]
|
||||
TimestampArgs = ArgumentsBase[time.Time, TimestampConfig, timestampValue]
|
||||
UintArgs = ArgumentsBase[uint, IntegerConfig, uintValue[uint]]
|
||||
Uint8Args = ArgumentsBase[uint8, IntegerConfig, uintValue[uint8]]
|
||||
Uint16Args = ArgumentsBase[uint16, IntegerConfig, uintValue[uint16]]
|
||||
Uint32Args = ArgumentsBase[uint32, IntegerConfig, uintValue[uint32]]
|
||||
Uint64Args = ArgumentsBase[uint64, IntegerConfig, uintValue[uint64]]
|
||||
)
|
||||
|
||||
func (c *Command) getArgValue(name string) any {
|
||||
tracef("command %s looking for args %s", c.Name, name)
|
||||
for _, arg := range c.Arguments {
|
||||
if arg.HasName(name) {
|
||||
tracef("command %s found args %s", c.Name, name)
|
||||
return arg.Get()
|
||||
}
|
||||
}
|
||||
tracef("command %s did not find args %s", c.Name, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func arg[T any](name string, c *Command) T {
|
||||
val := c.getArgValue(name)
|
||||
if a, ok := val.(T); ok {
|
||||
return a
|
||||
}
|
||||
var zero T
|
||||
return zero
|
||||
}
|
||||
|
||||
func (c *Command) StringArg(name string) string {
|
||||
return arg[string](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) StringArgs(name string) []string {
|
||||
return arg[[]string](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) FloatArg(name string) float64 {
|
||||
return arg[float64](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) FloatArgs(name string) []float64 {
|
||||
return arg[[]float64](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Float32Arg(name string) float32 {
|
||||
return arg[float32](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Float32Args(name string) []float32 {
|
||||
return arg[[]float32](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Float64Arg(name string) float64 {
|
||||
return arg[float64](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Float64Args(name string) []float64 {
|
||||
return arg[[]float64](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) IntArg(name string) int {
|
||||
return arg[int](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) IntArgs(name string) []int {
|
||||
return arg[[]int](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Int8Arg(name string) int8 {
|
||||
return arg[int8](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Int8Args(name string) []int8 {
|
||||
return arg[[]int8](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Int16Arg(name string) int16 {
|
||||
return arg[int16](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Int16Args(name string) []int16 {
|
||||
return arg[[]int16](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Int32Arg(name string) int32 {
|
||||
return arg[int32](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Int32Args(name string) []int32 {
|
||||
return arg[[]int32](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Int64Arg(name string) int64 {
|
||||
return arg[int64](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Int64Args(name string) []int64 {
|
||||
return arg[[]int64](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) UintArg(name string) uint {
|
||||
return arg[uint](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Uint8Arg(name string) uint8 {
|
||||
return arg[uint8](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Uint16Arg(name string) uint16 {
|
||||
return arg[uint16](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Uint32Arg(name string) uint32 {
|
||||
return arg[uint32](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Uint64Arg(name string) uint64 {
|
||||
return arg[uint64](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) UintArgs(name string) []uint {
|
||||
return arg[[]uint](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Uint8Args(name string) []uint8 {
|
||||
return arg[[]uint8](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Uint16Args(name string) []uint16 {
|
||||
return arg[[]uint16](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Uint32Args(name string) []uint32 {
|
||||
return arg[[]uint32](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Uint64Args(name string) []uint64 {
|
||||
return arg[[]uint64](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) TimestampArg(name string) time.Time {
|
||||
return arg[time.Time](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) TimestampArgs(name string) []time.Time {
|
||||
return arg[[]time.Time](name, c)
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
# This is a shell completion script auto-generated by https://github.com/urfave/cli for bash.
|
||||
|
||||
# macOS still ships Bash 3, where bash-completion may not provide
|
||||
# _init_completion. This is a minimal compatible fallback.
|
||||
__%[1]s_init_completion() {
|
||||
COMPREPLY=()
|
||||
if declare -F _comp_initialize >/dev/null 2>&1; then
|
||||
_comp_initialize "$@"
|
||||
else
|
||||
_get_comp_words_by_ref "$@" cur prev words cword
|
||||
fi
|
||||
}
|
||||
|
||||
__%[1]s_build_completion_request() {
|
||||
local -a words_before_cursor=("${COMP_WORDS[@]:0:${COMP_CWORD}}")
|
||||
local current_word="${COMP_WORDS[COMP_CWORD]}"
|
||||
|
||||
if [[ "${current_word}" == "-"* ]]; then
|
||||
printf '%%s %%s --generate-shell-completion' "${words_before_cursor[*]}" "${current_word}"
|
||||
else
|
||||
printf '%%s --generate-shell-completion' "${words_before_cursor[*]}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Keep Bash 3 compatibility: associative arrays require Bash 4+, so
|
||||
# descriptions are looked up via parallel indexed arrays.
|
||||
__%[1]s_find_description_for() {
|
||||
local candidate="$1"
|
||||
local i
|
||||
|
||||
for i in "${!__cli_completion_tokens[@]}"; do
|
||||
if [[ "${__cli_completion_tokens[i]}" == "${candidate}" ]]; then
|
||||
printf '%%s' "${__cli_completion_descriptions[i]}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
__%[1]s_bash_autocomplete() {
|
||||
local words=("${COMP_WORDS[@]}")
|
||||
|
||||
if [[ "${words[0]}" != "source" ]]; then
|
||||
local cur opts
|
||||
local cword="${COMP_CWORD}"
|
||||
local request_comp
|
||||
|
||||
COMPREPLY=()
|
||||
cur="${words[$cword]}"
|
||||
|
||||
__%[1]s_init_completion -n "=:" || return
|
||||
|
||||
request_comp="$(__%[1]s_build_completion_request)"
|
||||
opts=$(eval "${request_comp}" 2>/dev/null)
|
||||
|
||||
# Completion output lines use "token:description" format.
|
||||
# Keep token/description in parallel arrays for Bash 3 compatibility.
|
||||
__cli_completion_tokens=()
|
||||
__cli_completion_descriptions=()
|
||||
|
||||
local line
|
||||
local longest=0
|
||||
while IFS=$'\n' read -r line; do
|
||||
local token="${line}"
|
||||
local description=""
|
||||
|
||||
if [[ "${line}" == *:* ]]; then
|
||||
token="${line%%:*}"
|
||||
description="${line#*:}"
|
||||
fi
|
||||
|
||||
if [[ -z "${token}" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
__cli_completion_tokens+=("${token}")
|
||||
__cli_completion_descriptions+=("${description}")
|
||||
(( ${#token} > longest )) && longest=${#token}
|
||||
done <<< "${opts}"
|
||||
|
||||
local matches=( $(compgen -W "${__cli_completion_tokens[*]}" -- "${cur}") )
|
||||
|
||||
# COMP_TYPE=63 means Bash is listing matches (usually on second TAB).
|
||||
if [[ "${COMP_TYPE:-}" == "63" && ${#matches[@]} -gt 0 ]]; then
|
||||
local listed=()
|
||||
local candidate
|
||||
for candidate in "${matches[@]}"; do
|
||||
local desc="$(__%[1]s_find_description_for "${candidate}")"
|
||||
|
||||
if [[ -n "${desc}" ]]; then
|
||||
local padded="$(printf '%%-*s' "${longest}" "${candidate}")"
|
||||
listed+=("${padded} -- ${desc}")
|
||||
else
|
||||
listed+=("${candidate}")
|
||||
fi
|
||||
done
|
||||
COMPREPLY=("${listed[@]}")
|
||||
else
|
||||
COMPREPLY=("${matches[@]}")
|
||||
fi
|
||||
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
complete -o bashdefault -o default -o nospace -F __%[1]s_bash_autocomplete %[1]s
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# This is a shell completion script auto-generated by https://github.com/urfave/cli for fish.
|
||||
|
||||
function __%[1]s_perform_completion
|
||||
# Extract all args except the last one
|
||||
set -l args (commandline -opc)
|
||||
# Extract the last arg (partial input)
|
||||
set -l lastArg (commandline -ct)
|
||||
|
||||
set -l results ($args[1] $args[2..-1] $lastArg --generate-shell-completion 2> /dev/null)
|
||||
|
||||
# Remove trailing empty lines
|
||||
for line in $results[-1..1]
|
||||
if test (string trim -- $line) = ""
|
||||
set results $results[1..-2]
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
for line in $results
|
||||
if not string match -q -- "%[1]s*" $line
|
||||
set -l parts (string split -m 1 ":" -- "$line")
|
||||
if test (count $parts) -eq 2
|
||||
printf "%%s\t%%s\n" "$parts[1]" "$parts[2]"
|
||||
else
|
||||
printf "%%s\n" "$line"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Clear existing completions for %[1]s
|
||||
complete -c %[1]s -e
|
||||
# Register completion function
|
||||
complete -c %[1]s -f -a '(__%[1]s_perform_completion)'
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
$fn = $($MyInvocation.MyCommand.Name)
|
||||
$name = $fn -replace "(.*)\.ps1$", '$1'
|
||||
Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock {
|
||||
param($commandName, $wordToComplete, $cursorPosition)
|
||||
$other = "$wordToComplete --generate-shell-completion"
|
||||
Invoke-Expression $other | ForEach-Object {
|
||||
$parts = $_.Split(':', 2)
|
||||
if ($parts.Count -eq 2) {
|
||||
$completion = $parts[0].Trim()
|
||||
$description = $parts[1].Trim()
|
||||
[System.Management.Automation.CompletionResult]::new($completion, $completion, 'ParameterValue', $description)
|
||||
} else {
|
||||
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
#compdef %[1]s
|
||||
compdef _%[1]s %[1]s
|
||||
|
||||
# This is a shell completion script auto-generated by https://github.com/urfave/cli for zsh.
|
||||
|
||||
_%[1]s() {
|
||||
local -a opts # Declare a local array
|
||||
local current
|
||||
current=${words[-1]} # -1 means "the last element"
|
||||
if [[ "$current" == "-"* ]]; then
|
||||
# Current word starts with a hyphen, so complete flags/options
|
||||
opts=("${(@f)$(${words[@]:0:#words[@]-1} ${current} --generate-shell-completion)}")
|
||||
else
|
||||
# Current word does not start with a hyphen, so complete subcommands
|
||||
opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-shell-completion)}")
|
||||
fi
|
||||
|
||||
if [[ "${opts[1]}" != "" ]]; then
|
||||
_describe 'values' opts
|
||||
else
|
||||
_files
|
||||
fi
|
||||
}
|
||||
|
||||
# Don't run the completion function when being source-ed or eval-ed.
|
||||
# See https://github.com/urfave/cli/issues/1874 for discussion.
|
||||
if [ "$funcstack[1]" = "_%[1]s" ]; then
|
||||
_%[1]s
|
||||
fi
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
package cli
|
||||
|
||||
import "sort"
|
||||
|
||||
// CommandCategories interface allows for category manipulation
|
||||
type CommandCategories interface {
|
||||
// AddCommand adds a command to a category, creating a new category if necessary.
|
||||
AddCommand(category string, command *Command)
|
||||
// Categories returns a slice of categories sorted by name
|
||||
Categories() []CommandCategory
|
||||
}
|
||||
|
||||
type commandCategories []*commandCategory
|
||||
|
||||
func newCommandCategories() CommandCategories {
|
||||
ret := commandCategories([]*commandCategory{})
|
||||
return &ret
|
||||
}
|
||||
|
||||
func (c *commandCategories) Less(i, j int) bool {
|
||||
return lexicographicLess((*c)[i].Name(), (*c)[j].Name())
|
||||
}
|
||||
|
||||
func (c *commandCategories) Len() int {
|
||||
return len(*c)
|
||||
}
|
||||
|
||||
func (c *commandCategories) Swap(i, j int) {
|
||||
(*c)[i], (*c)[j] = (*c)[j], (*c)[i]
|
||||
}
|
||||
|
||||
func (c *commandCategories) AddCommand(category string, command *Command) {
|
||||
for _, commandCategory := range []*commandCategory(*c) {
|
||||
if commandCategory.name == category {
|
||||
commandCategory.commands = append(commandCategory.commands, command)
|
||||
return
|
||||
}
|
||||
}
|
||||
newVal := append(*c,
|
||||
&commandCategory{name: category, commands: []*Command{command}})
|
||||
*c = newVal
|
||||
}
|
||||
|
||||
func (c *commandCategories) Categories() []CommandCategory {
|
||||
ret := make([]CommandCategory, len(*c))
|
||||
for i, cat := range *c {
|
||||
ret[i] = cat
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// CommandCategory is a category containing commands.
|
||||
type CommandCategory interface {
|
||||
// Name returns the category name string
|
||||
Name() string
|
||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
||||
VisibleCommands() []*Command
|
||||
}
|
||||
|
||||
type commandCategory struct {
|
||||
name string
|
||||
commands []*Command
|
||||
}
|
||||
|
||||
func (c *commandCategory) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
func (c *commandCategory) VisibleCommands() []*Command {
|
||||
if c.commands == nil {
|
||||
c.commands = []*Command{}
|
||||
}
|
||||
|
||||
var ret []*Command
|
||||
for _, command := range c.commands {
|
||||
if !command.Hidden {
|
||||
ret = append(ret, command)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// FlagCategories interface allows for category manipulation
|
||||
type FlagCategories interface {
|
||||
// AddFlags adds a flag to a category, creating a new category if necessary.
|
||||
AddFlag(category string, fl Flag)
|
||||
// VisibleCategories returns a slice of visible flag categories sorted by name
|
||||
VisibleCategories() []VisibleFlagCategory
|
||||
}
|
||||
|
||||
type defaultFlagCategories struct {
|
||||
m map[string]*defaultVisibleFlagCategory
|
||||
}
|
||||
|
||||
func newFlagCategories() FlagCategories {
|
||||
return &defaultFlagCategories{
|
||||
m: map[string]*defaultVisibleFlagCategory{},
|
||||
}
|
||||
}
|
||||
|
||||
func newFlagCategoriesFromFlags(fs []Flag) FlagCategories {
|
||||
fc := newFlagCategories()
|
||||
|
||||
var categorized bool
|
||||
|
||||
for _, fl := range fs {
|
||||
if cf, ok := fl.(CategorizableFlag); ok {
|
||||
visible := false
|
||||
if vf, ok := fl.(VisibleFlag); ok {
|
||||
visible = vf.IsVisible()
|
||||
}
|
||||
if cat := cf.GetCategory(); cat != "" && visible {
|
||||
fc.AddFlag(cat, fl)
|
||||
categorized = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if categorized {
|
||||
for _, fl := range fs {
|
||||
if cf, ok := fl.(CategorizableFlag); ok {
|
||||
visible := false
|
||||
if vf, ok := fl.(VisibleFlag); ok {
|
||||
visible = vf.IsVisible()
|
||||
}
|
||||
if cf.GetCategory() == "" && visible {
|
||||
fc.AddFlag("", fl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fc
|
||||
}
|
||||
|
||||
func (f *defaultFlagCategories) AddFlag(category string, fl Flag) {
|
||||
if _, ok := f.m[category]; !ok {
|
||||
f.m[category] = &defaultVisibleFlagCategory{name: category, m: map[string]Flag{}}
|
||||
}
|
||||
|
||||
f.m[category].m[fl.String()] = fl
|
||||
}
|
||||
|
||||
func (f *defaultFlagCategories) VisibleCategories() []VisibleFlagCategory {
|
||||
catNames := []string{}
|
||||
for name := range f.m {
|
||||
catNames = append(catNames, name)
|
||||
}
|
||||
|
||||
sort.Strings(catNames)
|
||||
|
||||
ret := make([]VisibleFlagCategory, len(catNames))
|
||||
for i, name := range catNames {
|
||||
ret[i] = f.m[name]
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// VisibleFlagCategory is a category containing flags.
|
||||
type VisibleFlagCategory interface {
|
||||
// Name returns the category name string
|
||||
Name() string
|
||||
// Flags returns a slice of VisibleFlag sorted by name
|
||||
Flags() []Flag
|
||||
}
|
||||
|
||||
type defaultVisibleFlagCategory struct {
|
||||
name string
|
||||
m map[string]Flag
|
||||
}
|
||||
|
||||
func (fc *defaultVisibleFlagCategory) Name() string {
|
||||
return fc.name
|
||||
}
|
||||
|
||||
func (fc *defaultVisibleFlagCategory) Flags() []Flag {
|
||||
vfNames := []string{}
|
||||
for flName, fl := range fc.m {
|
||||
if vf, ok := fl.(VisibleFlag); ok {
|
||||
if vf.IsVisible() {
|
||||
vfNames = append(vfNames, flName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(vfNames)
|
||||
|
||||
ret := make([]Flag, len(vfNames))
|
||||
for i, flName := range vfNames {
|
||||
ret[i] = fc.m[flName]
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// Package cli provides a minimal framework for creating and organizing command line
|
||||
// Go applications. cli is designed to be easy to understand and write, the most simple
|
||||
// cli application can be written as follows:
|
||||
//
|
||||
// func main() {
|
||||
// (&cli.Command{}).Run(context.Background(), os.Args)
|
||||
// }
|
||||
//
|
||||
// Of course this application does not do much, so let's make this an actual application:
|
||||
//
|
||||
// func main() {
|
||||
// cmd := &cli.Command{
|
||||
// Name: "greet",
|
||||
// Usage: "say a greeting",
|
||||
// Action: func(c *cli.Context) error {
|
||||
// fmt.Println("Greetings")
|
||||
// return nil
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// cmd.Run(context.Background(), os.Args)
|
||||
// }
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var isTracingOn = os.Getenv("URFAVE_CLI_TRACING") == "on"
|
||||
|
||||
func tracef(format string, a ...any) {
|
||||
if !isTracingOn {
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(format, "\n") {
|
||||
format = format + "\n"
|
||||
}
|
||||
|
||||
pc, file, line, _ := runtime.Caller(1)
|
||||
cf := runtime.FuncForPC(pc)
|
||||
|
||||
fmt.Fprintf(
|
||||
os.Stderr,
|
||||
strings.Join([]string{
|
||||
"## URFAVE CLI TRACE ",
|
||||
file,
|
||||
":",
|
||||
fmt.Sprintf("%v", line),
|
||||
" ",
|
||||
fmt.Sprintf("(%s)", cf.Name()),
|
||||
" ",
|
||||
format,
|
||||
}, ""),
|
||||
a...,
|
||||
)
|
||||
}
|
||||
+604
@@ -0,0 +1,604 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// ignoreFlagPrefix is to ignore test flags when adding flags from other packages
|
||||
ignoreFlagPrefix = "test."
|
||||
|
||||
commandContextKey = contextKey("cli.context")
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
// Command contains everything needed to run an application that
|
||||
// accepts a string slice of arguments such as os.Args. A given
|
||||
// Command may contain Flags and sub-commands in Commands.
|
||||
type Command struct {
|
||||
// The name of the command
|
||||
Name string `json:"name"`
|
||||
// A list of aliases for the command
|
||||
Aliases []string `json:"aliases"`
|
||||
// A short description of the usage of this command
|
||||
Usage string `json:"usage"`
|
||||
// Text to override the USAGE section of help
|
||||
UsageText string `json:"usageText"`
|
||||
// A short description of the arguments of this command
|
||||
ArgsUsage string `json:"argsUsage"`
|
||||
// Version of the command
|
||||
Version string `json:"version"`
|
||||
// Longer explanation of how the command works
|
||||
Description string `json:"description"`
|
||||
// DefaultCommand is the (optional) name of a command
|
||||
// to run if no command names are passed as CLI arguments.
|
||||
DefaultCommand string `json:"defaultCommand"`
|
||||
// The category the command is part of
|
||||
Category string `json:"category"`
|
||||
// List of child commands
|
||||
Commands []*Command `json:"commands"`
|
||||
// List of flags to parse
|
||||
Flags []Flag `json:"flags"`
|
||||
// Boolean to hide built-in help command and help flag
|
||||
HideHelp bool `json:"hideHelp"`
|
||||
// Ignored if HideHelp is true.
|
||||
HideHelpCommand bool `json:"hideHelpCommand"`
|
||||
// Boolean to hide built-in version flag and the VERSION section of help
|
||||
HideVersion bool `json:"hideVersion"`
|
||||
// Boolean to enable shell completion commands
|
||||
EnableShellCompletion bool `json:"-"`
|
||||
// Shell Completion generation command name
|
||||
ShellCompletionCommandName string `json:"-"`
|
||||
// The function to call when checking for shell command completions
|
||||
ShellComplete ShellCompleteFunc `json:"-"`
|
||||
// The function to configure a shell completion command
|
||||
ConfigureShellCompletionCommand ConfigureShellCompletionCommand `json:"-"`
|
||||
// An action to execute before any subcommands are run, but after the context is ready
|
||||
// If a non-nil error is returned, no subcommands are run
|
||||
Before BeforeFunc `json:"-"`
|
||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
||||
// It is run even if Action() panics
|
||||
After AfterFunc `json:"-"`
|
||||
// The function to call when this command is invoked
|
||||
Action ActionFunc `json:"-"`
|
||||
// Execute this function if the proper command cannot be found
|
||||
CommandNotFound CommandNotFoundFunc `json:"-"`
|
||||
// Execute this function if a usage error occurs.
|
||||
OnUsageError OnUsageErrorFunc `json:"-"`
|
||||
// Execute this function when an invalid flag is accessed from the context
|
||||
InvalidFlagAccessHandler InvalidFlagAccessFunc `json:"-"`
|
||||
// Boolean to hide this command from help or completion
|
||||
Hidden bool `json:"hidden"`
|
||||
// List of all authors who contributed (string or fmt.Stringer)
|
||||
// TODO: ~string | fmt.Stringer when interface unions are available
|
||||
Authors []any `json:"authors"`
|
||||
// Copyright of the binary if any
|
||||
Copyright string `json:"copyright"`
|
||||
// Reader reader to write input to (useful for tests)
|
||||
Reader io.Reader `json:"-"`
|
||||
// Writer writer to write output to
|
||||
Writer io.Writer `json:"-"`
|
||||
// ErrWriter writes error output
|
||||
ErrWriter io.Writer `json:"-"`
|
||||
// ExitErrHandler processes any error encountered while running a Command before it is
|
||||
// returned to the caller. If no function is provided, HandleExitCoder is used as the
|
||||
// default behavior.
|
||||
ExitErrHandler ExitErrHandlerFunc `json:"-"`
|
||||
// Other custom info
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
// Carries a function which returns app specific info.
|
||||
ExtraInfo func() map[string]string `json:"-"`
|
||||
// CustomRootCommandHelpTemplate the text template for app help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
CustomRootCommandHelpTemplate string `json:"-"`
|
||||
// SliceFlagSeparator is used to customize the separator for SliceFlag, the default is ","
|
||||
SliceFlagSeparator string `json:"sliceFlagSeparator"`
|
||||
// DisableSliceFlagSeparator is used to disable SliceFlagSeparator, the default is false
|
||||
DisableSliceFlagSeparator bool `json:"disableSliceFlagSeparator"`
|
||||
// MapFlagKeyValueSeparator is used to customize the separator for MapFlag, the default is "="
|
||||
MapFlagKeyValueSeparator string `json:"mapFlagKeyValueSeparator"`
|
||||
// Boolean to enable short-option handling so user can combine several
|
||||
// single-character bool arguments into one
|
||||
// i.e. foobar -o -v -> foobar -ov
|
||||
UseShortOptionHandling bool `json:"useShortOptionHandling"`
|
||||
// Enable suggestions for commands and flags
|
||||
Suggest bool `json:"suggest"`
|
||||
// Allows global flags set by libraries which use flag.XXXVar(...) directly
|
||||
// to be parsed through this library
|
||||
AllowExtFlags bool `json:"allowExtFlags"`
|
||||
// Treat all flags as normal arguments if true
|
||||
SkipFlagParsing bool `json:"skipFlagParsing"`
|
||||
// CustomHelpTemplate the text template for the command help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
CustomHelpTemplate string `json:"-"`
|
||||
// Use longest prefix match for commands
|
||||
PrefixMatchCommands bool `json:"prefixMatchCommands"`
|
||||
// Custom suggest command for matching
|
||||
SuggestCommandFunc SuggestCommandFunc `json:"-"`
|
||||
// Flag exclusion group
|
||||
MutuallyExclusiveFlags []MutuallyExclusiveFlags `json:"mutuallyExclusiveFlags"`
|
||||
// Arguments to parse for this command
|
||||
Arguments []Argument `json:"arguments"`
|
||||
// Whether to read arguments from stdin
|
||||
// applicable to root command only
|
||||
ReadArgsFromStdin bool `json:"readArgsFromStdin"`
|
||||
// StopOnNthArg provides v2-like behavior for specific commands by stopping
|
||||
// flag parsing after N positional arguments are encountered. When set to N,
|
||||
// all remaining arguments after the Nth positional argument will be treated
|
||||
// as arguments, not flags.
|
||||
//
|
||||
// A value of 0 means all arguments are treated as positional (no flag parsing).
|
||||
// A nil value means normal v3 flag parsing behavior (flags can appear anywhere).
|
||||
StopOnNthArg *int `json:"stopOnNthArg"`
|
||||
|
||||
// categories contains the categorized commands and is populated on app startup
|
||||
categories CommandCategories
|
||||
// flagCategories contains the categorized flags and is populated on app startup
|
||||
flagCategories FlagCategories
|
||||
// flags that have been applied in current parse
|
||||
appliedFlags []Flag
|
||||
// flags that have been set
|
||||
setFlags map[Flag]struct{}
|
||||
// The parent of this command. This value will be nil for the
|
||||
// command at the root of the graph.
|
||||
parent *Command
|
||||
// parsed args
|
||||
parsedArgs Args
|
||||
// track state of error handling
|
||||
isInError bool
|
||||
// track state of defaults
|
||||
didSetupDefaults bool
|
||||
// whether in shell completion mode
|
||||
shellCompletion bool
|
||||
// whether global help flag was added
|
||||
globaHelpFlagAdded bool
|
||||
// whether global version flag was added
|
||||
globaVersionFlagAdded bool
|
||||
// whether this is a completion command
|
||||
isCompletionCommand bool
|
||||
}
|
||||
|
||||
// FullName returns the full name of the command.
|
||||
// For commands with parents this ensures that the parent commands
|
||||
// are part of the command path.
|
||||
func (cmd *Command) FullName() string {
|
||||
namePath := []string{}
|
||||
|
||||
if cmd.parent != nil {
|
||||
namePath = append(namePath, cmd.parent.FullName())
|
||||
}
|
||||
|
||||
return strings.Join(append(namePath, cmd.Name), " ")
|
||||
}
|
||||
|
||||
func (cmd *Command) Command(name string) *Command {
|
||||
for _, subCmd := range cmd.Commands {
|
||||
if subCmd.HasName(name) {
|
||||
return subCmd
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Command) checkHelp() bool {
|
||||
tracef("checking if help is wanted (cmd=%[1]q)", cmd.Name)
|
||||
|
||||
return HelpFlag != nil && slices.ContainsFunc(HelpFlag.Names(), cmd.Bool)
|
||||
}
|
||||
|
||||
func (cmd *Command) allFlags() []Flag {
|
||||
var flags []Flag
|
||||
flags = append(flags, cmd.Flags...)
|
||||
for _, grpf := range cmd.MutuallyExclusiveFlags {
|
||||
for _, f1 := range grpf.Flags {
|
||||
flags = append(flags, f1...)
|
||||
}
|
||||
}
|
||||
return flags
|
||||
}
|
||||
|
||||
// useShortOptionHandling traverses Lineage() for *any* ancestors
|
||||
// with UseShortOptionHandling
|
||||
func (cmd *Command) useShortOptionHandling() bool {
|
||||
for _, pCmd := range cmd.Lineage() {
|
||||
if pCmd.UseShortOptionHandling {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd *Command) suggestFlagFromError(err error, commandName string) (string, error) {
|
||||
fl, parseErr := flagFromError(err)
|
||||
if parseErr != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
flags := cmd.Flags
|
||||
hideHelp := cmd.hideHelp()
|
||||
|
||||
if commandName != "" {
|
||||
subCmd := cmd.Command(commandName)
|
||||
if subCmd == nil {
|
||||
return "", err
|
||||
}
|
||||
flags = subCmd.Flags
|
||||
hideHelp = hideHelp || subCmd.HideHelp
|
||||
}
|
||||
|
||||
suggestion := SuggestFlag(flags, fl, hideHelp)
|
||||
if len(suggestion) == 0 {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf(SuggestDidYouMeanTemplate, suggestion) + "\n\n", nil
|
||||
}
|
||||
|
||||
// Names returns the names including short names and aliases.
|
||||
func (cmd *Command) Names() []string {
|
||||
return append([]string{cmd.Name}, cmd.Aliases...)
|
||||
}
|
||||
|
||||
// HasName returns true if Command.Name matches given name
|
||||
func (cmd *Command) HasName(name string) bool {
|
||||
return slices.Contains(cmd.Names(), name)
|
||||
}
|
||||
|
||||
// VisibleCategories returns a slice of categories and commands that are
|
||||
// Hidden=false
|
||||
func (cmd *Command) VisibleCategories() []CommandCategory {
|
||||
ret := []CommandCategory{}
|
||||
for _, category := range cmd.categories.Categories() {
|
||||
if visible := func() CommandCategory {
|
||||
if len(category.VisibleCommands()) > 0 {
|
||||
return category
|
||||
}
|
||||
return nil
|
||||
}(); visible != nil {
|
||||
ret = append(ret, visible)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
||||
func (cmd *Command) VisibleCommands() []*Command {
|
||||
var ret []*Command
|
||||
for _, command := range cmd.Commands {
|
||||
if command.Hidden || command.Name == helpName {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, command)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain
|
||||
func (cmd *Command) VisibleFlagCategories() []VisibleFlagCategory {
|
||||
if cmd.flagCategories == nil {
|
||||
cmd.flagCategories = newFlagCategoriesFromFlags(cmd.allFlags())
|
||||
}
|
||||
return cmd.flagCategories.VisibleCategories()
|
||||
}
|
||||
|
||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
||||
func (cmd *Command) VisibleFlags() []Flag {
|
||||
return visibleFlags(cmd.allFlags())
|
||||
}
|
||||
|
||||
func (cmd *Command) appendFlag(fl Flag) {
|
||||
if !hasFlag(cmd.Flags, fl) {
|
||||
cmd.Flags = append(cmd.Flags, fl)
|
||||
}
|
||||
}
|
||||
|
||||
// VisiblePersistentFlags returns a slice of [LocalFlag] with Persistent=true and Hidden=false.
|
||||
func (cmd *Command) VisiblePersistentFlags() []Flag {
|
||||
var flags []Flag
|
||||
for _, fl := range cmd.Root().Flags {
|
||||
pfl, ok := fl.(LocalFlag)
|
||||
if !ok || pfl.IsLocal() {
|
||||
continue
|
||||
}
|
||||
flags = append(flags, fl)
|
||||
}
|
||||
return visibleFlags(flags)
|
||||
}
|
||||
|
||||
func (cmd *Command) appendCommand(aCmd *Command) {
|
||||
if !slices.Contains(cmd.Commands, aCmd) {
|
||||
aCmd.parent = cmd
|
||||
cmd.Commands = append(cmd.Commands, aCmd)
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Command) handleExitCoder(ctx context.Context, err error) error {
|
||||
if cmd.parent != nil {
|
||||
return cmd.parent.handleExitCoder(ctx, err)
|
||||
}
|
||||
|
||||
if cmd.ExitErrHandler != nil {
|
||||
cmd.ExitErrHandler(ctx, cmd, err)
|
||||
return err
|
||||
}
|
||||
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (cmd *Command) argsWithDefaultCommand(oldArgs Args) Args {
|
||||
rawArgs := append([]string{cmd.DefaultCommand}, oldArgs.Slice()...)
|
||||
newArgs := &stringSliceArgs{v: rawArgs}
|
||||
|
||||
return newArgs
|
||||
}
|
||||
|
||||
// Root returns the Command at the root of the graph
|
||||
func (cmd *Command) Root() *Command {
|
||||
if cmd.parent == nil {
|
||||
return cmd
|
||||
}
|
||||
|
||||
return cmd.parent.Root()
|
||||
}
|
||||
|
||||
func (cmd *Command) set(fName string, f Flag, val string) error {
|
||||
cmd.setFlags[f] = struct{}{}
|
||||
cmd.setMultiValueParsingConfig(f)
|
||||
if err := f.Set(fName, val); err != nil {
|
||||
return fmt.Errorf("invalid value %q for flag -%s: %v", val, fName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Command) lFlag(name string) Flag {
|
||||
for _, f := range cmd.allFlags() {
|
||||
if slices.Contains(f.Names(), name) {
|
||||
tracef("flag found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return f
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Command) lookupFlag(name string) Flag {
|
||||
for _, pCmd := range cmd.Lineage() {
|
||||
if f := pCmd.lFlag(name); f != nil {
|
||||
return f
|
||||
}
|
||||
}
|
||||
|
||||
tracef("flag NOT found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
cmd.onInvalidFlag(context.TODO(), name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// this looks up only allowed flags, i.e. local flags for current command
|
||||
// or persistent flags from ancestors
|
||||
func (cmd *Command) lookupAppliedFlag(name string) Flag {
|
||||
for _, f := range cmd.appliedFlags {
|
||||
if slices.Contains(f.Names(), name) {
|
||||
tracef("appliedFlag found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return f
|
||||
}
|
||||
}
|
||||
|
||||
tracef("lookupAppliedflag NOT found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
cmd.onInvalidFlag(context.TODO(), name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Command) checkRequiredFlag(f Flag) (bool, string) {
|
||||
if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() {
|
||||
flagName := f.Names()[0]
|
||||
if !f.IsSet() {
|
||||
return false, flagName
|
||||
}
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (cmd *Command) checkAllRequiredFlags() requiredFlagsErr {
|
||||
// The help and completion commands are allowed to run without
|
||||
// enforcement of required flags, since they do not invoke user
|
||||
// actions that depend on those flag values.
|
||||
if cmd.Name == helpName || cmd.isCompletionCommand {
|
||||
return nil
|
||||
}
|
||||
for pCmd := cmd; pCmd != nil; pCmd = pCmd.parent {
|
||||
if err := pCmd.checkRequiredFlags(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Command) checkRequiredFlags() requiredFlagsErr {
|
||||
tracef("checking for required flags (cmd=%[1]q)", cmd.Name)
|
||||
|
||||
missingFlags := []string{}
|
||||
|
||||
for _, f := range cmd.appliedFlags {
|
||||
if ok, name := cmd.checkRequiredFlag(f); !ok {
|
||||
missingFlags = append(missingFlags, name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingFlags) != 0 {
|
||||
tracef("found missing required flags %[1]q (cmd=%[2]q)", missingFlags, cmd.Name)
|
||||
|
||||
return &errRequiredFlags{missingFlags: missingFlags}
|
||||
}
|
||||
|
||||
tracef("all required flags set (cmd=%[1]q)", cmd.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Command) onInvalidFlag(ctx context.Context, name string) {
|
||||
for cmd != nil {
|
||||
if cmd.InvalidFlagAccessHandler != nil {
|
||||
cmd.InvalidFlagAccessHandler(ctx, cmd, name)
|
||||
break
|
||||
}
|
||||
cmd = cmd.parent
|
||||
}
|
||||
}
|
||||
|
||||
// NumFlags returns the number of flags set
|
||||
func (cmd *Command) NumFlags() int {
|
||||
tracef("numFlags numAppliedFlags %d", len(cmd.appliedFlags))
|
||||
count := 0
|
||||
for _, f := range cmd.appliedFlags {
|
||||
if f.IsSet() {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count // cmd.flagSet.NFlag()
|
||||
}
|
||||
|
||||
func (cmd *Command) setMultiValueParsingConfig(f Flag) {
|
||||
tracef("setMultiValueParsingConfig %T, %+v", f, f)
|
||||
if cf, ok := f.(multiValueParsingConfigSetter); ok {
|
||||
cf.setMultiValueParsingConfig(multiValueParsingConfig{
|
||||
SliceFlagSeparator: cmd.SliceFlagSeparator,
|
||||
DisableSliceFlagSeparator: cmd.DisableSliceFlagSeparator,
|
||||
MapFlagKeyValueSeparator: cmd.MapFlagKeyValueSeparator,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets a context flag to a value.
|
||||
func (cmd *Command) Set(name, value string) error {
|
||||
if f := cmd.lookupFlag(name); f != nil {
|
||||
cmd.setMultiValueParsingConfig(f)
|
||||
return f.Set(name, value)
|
||||
}
|
||||
|
||||
return fmt.Errorf("no such flag -%s", name)
|
||||
}
|
||||
|
||||
// IsSet determines if the flag was actually set
|
||||
func (cmd *Command) IsSet(name string) bool {
|
||||
fl := cmd.lookupFlag(name)
|
||||
if fl == nil {
|
||||
tracef("flag with name %[1]q NOT found; assuming not set (cmd=%[2]q)", name, cmd.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
isSet := fl.IsSet()
|
||||
if isSet {
|
||||
tracef("flag with name %[1]q is set (cmd=%[2]q)", name, cmd.Name)
|
||||
} else {
|
||||
tracef("flag with name %[1]q is no set (cmd=%[2]q)", name, cmd.Name)
|
||||
}
|
||||
|
||||
return isSet
|
||||
}
|
||||
|
||||
// LocalFlagNames returns a slice of flag names used in this
|
||||
// command.
|
||||
func (cmd *Command) LocalFlagNames() []string {
|
||||
names := []string{}
|
||||
|
||||
// Check the flags which have been set via env or file
|
||||
for _, f := range cmd.allFlags() {
|
||||
if f.IsSet() {
|
||||
names = append(names, f.Names()...)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort out the duplicates since flag could be set via multiple
|
||||
// paths
|
||||
m := map[string]struct{}{}
|
||||
uniqNames := []string{}
|
||||
|
||||
for _, name := range names {
|
||||
if _, ok := m[name]; !ok {
|
||||
m[name] = struct{}{}
|
||||
uniqNames = append(uniqNames, name)
|
||||
}
|
||||
}
|
||||
|
||||
return uniqNames
|
||||
}
|
||||
|
||||
// FlagNames returns a slice of flag names used by the this command
|
||||
// and all of its parent commands.
|
||||
func (cmd *Command) FlagNames() []string {
|
||||
names := cmd.LocalFlagNames()
|
||||
|
||||
if cmd.parent != nil {
|
||||
names = append(cmd.parent.FlagNames(), names...)
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
// Lineage returns *this* command and all of its ancestor commands
|
||||
// in order from child to parent
|
||||
func (cmd *Command) Lineage() []*Command {
|
||||
lineage := []*Command{cmd}
|
||||
|
||||
if cmd.parent != nil {
|
||||
lineage = append(lineage, cmd.parent.Lineage()...)
|
||||
}
|
||||
|
||||
return lineage
|
||||
}
|
||||
|
||||
// Count returns the num of occurrences of this flag
|
||||
func (cmd *Command) Count(name string) int {
|
||||
if cf, ok := cmd.lookupFlag(name).(Countable); ok {
|
||||
return cf.Count()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Value returns the value of the flag corresponding to `name`
|
||||
func (cmd *Command) Value(name string) any {
|
||||
if fs := cmd.lookupFlag(name); fs != nil {
|
||||
tracef("value found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return fs.Get()
|
||||
}
|
||||
|
||||
tracef("value NOT found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Args returns the command line arguments associated with the
|
||||
// command.
|
||||
func (cmd *Command) Args() Args {
|
||||
return cmd.parsedArgs
|
||||
}
|
||||
|
||||
// NArg returns the number of the command line arguments.
|
||||
func (cmd *Command) NArg() int {
|
||||
return cmd.Args().Len()
|
||||
}
|
||||
|
||||
func (cmd *Command) runFlagActions(ctx context.Context) error {
|
||||
tracef("runFlagActions")
|
||||
// run the flag actions in the same order that they are defined
|
||||
// to maintain consistency.
|
||||
for _, fl := range cmd.appliedFlags {
|
||||
if _, inSet := cmd.setFlags[fl]; inSet {
|
||||
if af, ok := fl.(ActionableFlag); ok {
|
||||
if err := af.RunAction(ctx, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
providedButNotDefinedErrMsg = "flag provided but not defined: -"
|
||||
argumentNotProvidedErrMsg = "flag needs an argument: "
|
||||
)
|
||||
|
||||
// flagFromError tries to parse a provided flag from an error message. If the
|
||||
// parsing fails, it returns the input error and an empty string
|
||||
func flagFromError(err error) (string, error) {
|
||||
errStr := err.Error()
|
||||
trimmed := strings.TrimPrefix(errStr, providedButNotDefinedErrMsg)
|
||||
if errStr == trimmed {
|
||||
return "", err
|
||||
}
|
||||
return trimmed, nil
|
||||
}
|
||||
|
||||
func (cmd *Command) parseFlags(args Args) (Args, error) {
|
||||
tracef("parsing flags from arguments %[1]q (cmd=%[2]q)", args, cmd.Name)
|
||||
|
||||
cmd.setFlags = map[Flag]struct{}{}
|
||||
cmd.appliedFlags = cmd.allFlags()
|
||||
|
||||
tracef("walking command lineage for persistent flags (cmd=%[1]q)", cmd.Name)
|
||||
|
||||
for pCmd := cmd.parent; pCmd != nil; pCmd = pCmd.parent {
|
||||
tracef(
|
||||
"checking ancestor command=%[1]q for persistent flags (cmd=%[2]q)",
|
||||
pCmd.Name, cmd.Name,
|
||||
)
|
||||
|
||||
for _, fl := range pCmd.allFlags() {
|
||||
flNames := fl.Names()
|
||||
|
||||
pfl, ok := fl.(LocalFlag)
|
||||
if !ok || pfl.IsLocal() {
|
||||
tracef("skipping non-persistent flag %[1]q (cmd=%[2]q)", flNames, cmd.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
tracef(
|
||||
"checking for applying persistent flag=%[1]q pCmd=%[2]q (cmd=%[3]q)",
|
||||
flNames, pCmd.Name, cmd.Name,
|
||||
)
|
||||
|
||||
applyPersistentFlag := true
|
||||
|
||||
for _, name := range flNames {
|
||||
if cmd.lFlag(name) != nil {
|
||||
applyPersistentFlag = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !applyPersistentFlag {
|
||||
tracef("not applying as persistent flag=%[1]q (cmd=%[2]q)", flNames, cmd.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
tracef("applying as persistent flag=%[1]q (cmd=%[2]q)", flNames, cmd.Name)
|
||||
|
||||
tracef("appending to applied flags flag=%[1]q (cmd=%[2]q)", flNames, cmd.Name)
|
||||
cmd.appliedFlags = append(cmd.appliedFlags, fl)
|
||||
}
|
||||
}
|
||||
|
||||
tracef("parsing flags iteratively tail=%[1]q (cmd=%[2]q)", args.Tail(), cmd.Name)
|
||||
defer tracef("done parsing flags (cmd=%[1]q)", cmd.Name)
|
||||
|
||||
posArgs := []string{}
|
||||
for rargs := args.Slice(); len(rargs) > 0; rargs = rargs[1:] {
|
||||
tracef("rearrange:1 (cmd=%[1]q) %[2]q", cmd.Name, rargs)
|
||||
|
||||
firstArg := strings.TrimSpace(rargs[0])
|
||||
if len(firstArg) == 0 {
|
||||
posArgs = append(posArgs, rargs[0])
|
||||
continue
|
||||
}
|
||||
|
||||
// stop parsing once we see a "--"
|
||||
if firstArg == "--" {
|
||||
// In shell completion mode, preserve "--" so that completion can detect
|
||||
// when the user is completing "--" itself vs. completing after "--"
|
||||
if cmd.Root().shellCompletion {
|
||||
posArgs = append(posArgs, firstArg)
|
||||
}
|
||||
posArgs = append(posArgs, rargs[1:]...)
|
||||
return &stringSliceArgs{posArgs}, nil
|
||||
}
|
||||
|
||||
// Check if we've reached the Nth argument and should stop flag parsing
|
||||
if cmd.StopOnNthArg != nil && len(posArgs) == *cmd.StopOnNthArg {
|
||||
// Append current arg and all remaining args without parsing
|
||||
posArgs = append(posArgs, rargs[0:]...)
|
||||
return &stringSliceArgs{posArgs}, nil
|
||||
}
|
||||
|
||||
// handle positional args
|
||||
if firstArg[0] != '-' {
|
||||
// positional argument probably
|
||||
tracef("rearrange-3 (cmd=%[1]q) check %[2]q", cmd.Name, firstArg)
|
||||
|
||||
// if there is a command by that name let the command handle the
|
||||
// rest of the parsing
|
||||
if cmd.Command(firstArg) != nil {
|
||||
posArgs = append(posArgs, rargs...)
|
||||
return &stringSliceArgs{posArgs}, nil
|
||||
}
|
||||
|
||||
posArgs = append(posArgs, firstArg)
|
||||
continue
|
||||
}
|
||||
|
||||
numMinuses := 1
|
||||
// this is same as firstArg == "-"
|
||||
if len(firstArg) == 1 {
|
||||
posArgs = append(posArgs, firstArg)
|
||||
break
|
||||
}
|
||||
|
||||
shortOptionHandling := cmd.useShortOptionHandling()
|
||||
|
||||
// stop parsing -- as short flags
|
||||
if firstArg[1] == '-' {
|
||||
numMinuses++
|
||||
shortOptionHandling = false
|
||||
} else if !unicode.IsLetter(rune(firstArg[1])) {
|
||||
// this is not a flag
|
||||
tracef("parseFlags not a unicode letter. Stop parsing")
|
||||
posArgs = append(posArgs, rargs...)
|
||||
return &stringSliceArgs{posArgs}, nil
|
||||
}
|
||||
|
||||
tracef("parseFlags (shortOptionHandling=%[1]q)", shortOptionHandling)
|
||||
|
||||
flagName := firstArg[numMinuses:]
|
||||
flagVal := ""
|
||||
valFromEqual := false
|
||||
tracef("flagName:1 (fName=%[1]q)", flagName)
|
||||
if index := strings.Index(flagName, "="); index != -1 {
|
||||
flagVal = flagName[index+1:]
|
||||
flagName = flagName[:index]
|
||||
valFromEqual = true
|
||||
}
|
||||
|
||||
tracef("flagName:2 (fName=%[1]q) (fVal=%[2]q)", flagName, flagVal)
|
||||
|
||||
f := cmd.lookupAppliedFlag(flagName)
|
||||
// found a flag matching given flagName
|
||||
if f != nil {
|
||||
tracef("Trying flag type (fName=%[1]q) (type=%[2]T)", flagName, f)
|
||||
if fb, ok := f.(boolFlag); ok && fb.IsBoolFlag() {
|
||||
if flagVal == "" {
|
||||
flagVal = "true"
|
||||
}
|
||||
tracef("parse Apply bool flag (fName=%[1]q) (fVal=%[2]q)", flagName, flagVal)
|
||||
if err := cmd.set(flagName, f, flagVal); err != nil {
|
||||
return &stringSliceArgs{posArgs}, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
tracef("processing non bool flag (fName=%[1]q)", flagName)
|
||||
// not a bool flag so need to get the next arg
|
||||
if flagVal == "" && !valFromEqual {
|
||||
if len(rargs) == 1 {
|
||||
// In shell completion mode, preserve the flag so that DefaultCompleteWithFlags can use it
|
||||
// as lastArg and offer suggestions for it.
|
||||
if cmd.Root().shellCompletion {
|
||||
posArgs = append(posArgs, rargs...)
|
||||
return &stringSliceArgs{posArgs}, nil
|
||||
}
|
||||
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", argumentNotProvidedErrMsg, firstArg)
|
||||
}
|
||||
flagVal = rargs[1]
|
||||
rargs = rargs[1:]
|
||||
}
|
||||
|
||||
tracef("setting non bool flag (fName=%[1]q) (fVal=%[2]q)", flagName, flagVal)
|
||||
if err := cmd.set(flagName, f, flagVal); err != nil {
|
||||
return &stringSliceArgs{posArgs}, err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// no flag lookup found and short handling is disabled
|
||||
if !shortOptionHandling {
|
||||
// In shell completion mode, preserve the partial flag so that DefaultCompleteWithFlags can use it
|
||||
// as lastArg and offer suggestions that match the prefix.
|
||||
if cmd.Root().shellCompletion {
|
||||
posArgs = append(posArgs, rargs...)
|
||||
return &stringSliceArgs{posArgs}, nil
|
||||
}
|
||||
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", providedButNotDefinedErrMsg, flagName)
|
||||
}
|
||||
|
||||
// try to split the flags
|
||||
for index, c := range flagName {
|
||||
tracef("processing flag (fName=%[1]q)", string(c))
|
||||
if sf := cmd.lookupFlag(string(c)); sf == nil {
|
||||
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", providedButNotDefinedErrMsg, flagName)
|
||||
} else if fb, ok := sf.(boolFlag); ok && fb.IsBoolFlag() {
|
||||
fv := flagVal
|
||||
if index == (len(flagName)-1) && flagVal == "" {
|
||||
fv = "true"
|
||||
}
|
||||
if fv == "" {
|
||||
fv = "true"
|
||||
}
|
||||
if err := cmd.set(flagName, sf, fv); err != nil {
|
||||
tracef("processing flag.2 (fName=%[1]q)", string(c))
|
||||
return &stringSliceArgs{posArgs}, err
|
||||
}
|
||||
} else if index == len(flagName)-1 { // last flag can take an arg
|
||||
if flagVal == "" {
|
||||
if len(rargs) == 1 {
|
||||
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", argumentNotProvidedErrMsg, string(c))
|
||||
}
|
||||
flagVal = rargs[1]
|
||||
rargs = rargs[1:]
|
||||
}
|
||||
tracef("parseFlags (flagName %[1]q) (flagVal %[2]q)", flagName, flagVal)
|
||||
if err := cmd.set(flagName, sf, flagVal); err != nil {
|
||||
tracef("processing flag.4 (fName=%[1]q)", string(c))
|
||||
return &stringSliceArgs{posArgs}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracef("returning-2 (cmd=%[1]q) args %[2]q", cmd.Name, posArgs)
|
||||
return &stringSliceArgs{posArgs}, nil
|
||||
}
|
||||
+372
@@ -0,0 +1,372 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func (cmd *Command) parseArgsFromStdin() ([]string, error) {
|
||||
type state int
|
||||
const (
|
||||
stateSearchForToken state = -1
|
||||
stateSearchForString state = 0
|
||||
)
|
||||
|
||||
st := stateSearchForToken
|
||||
linenum := 1
|
||||
token := ""
|
||||
args := []string{}
|
||||
|
||||
breader := bufio.NewReader(cmd.Reader)
|
||||
|
||||
outer:
|
||||
for {
|
||||
ch, _, err := breader.ReadRune()
|
||||
if err == io.EOF {
|
||||
switch st {
|
||||
case stateSearchForToken:
|
||||
if token != "--" {
|
||||
args = append(args, token)
|
||||
}
|
||||
case stateSearchForString:
|
||||
// make sure string is not empty
|
||||
for _, t := range token {
|
||||
if !unicode.IsSpace(t) {
|
||||
args = append(args, token)
|
||||
}
|
||||
}
|
||||
}
|
||||
break outer
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch st {
|
||||
case stateSearchForToken:
|
||||
if unicode.IsSpace(ch) || ch == '"' {
|
||||
if ch == '\n' {
|
||||
linenum++
|
||||
}
|
||||
if token != "" {
|
||||
// end the processing here
|
||||
if token == "--" {
|
||||
break outer
|
||||
}
|
||||
args = append(args, token)
|
||||
token = ""
|
||||
}
|
||||
if ch == '"' {
|
||||
st = stateSearchForString
|
||||
}
|
||||
continue
|
||||
}
|
||||
token += string(ch)
|
||||
case stateSearchForString:
|
||||
if ch != '"' {
|
||||
token += string(ch)
|
||||
} else {
|
||||
if token != "" {
|
||||
args = append(args, token)
|
||||
token = ""
|
||||
}
|
||||
/*else {
|
||||
//TODO. Should we pass in empty strings ?
|
||||
}*/
|
||||
st = stateSearchForToken
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracef("parsed stdin args as %v (cmd=%[2]q)", args, cmd.Name)
|
||||
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// Run is the entry point to the command graph. The positional
|
||||
// arguments are parsed according to the Flag and Command
|
||||
// definitions and the matching Action functions are run.
|
||||
func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error) {
|
||||
_, deferErr = cmd.run(ctx, osArgs)
|
||||
return deferErr
|
||||
}
|
||||
|
||||
func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context, deferErr error) {
|
||||
tracef("running with arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)
|
||||
cmd.setupDefaults(osArgs)
|
||||
|
||||
// Validate StopOnNthArg
|
||||
if cmd.StopOnNthArg != nil && *cmd.StopOnNthArg < 0 {
|
||||
return ctx, fmt.Errorf("StopOnNthArg must be non-negative, got %d", *cmd.StopOnNthArg)
|
||||
}
|
||||
|
||||
if v, ok := ctx.Value(commandContextKey).(*Command); ok {
|
||||
tracef("setting parent (cmd=%[1]q) command from context.Context value (cmd=%[2]q)", v.Name, cmd.Name)
|
||||
cmd.parent = v
|
||||
}
|
||||
|
||||
if cmd.parent == nil {
|
||||
if cmd.ReadArgsFromStdin {
|
||||
if args, err := cmd.parseArgsFromStdin(); err != nil {
|
||||
return ctx, err
|
||||
} else {
|
||||
osArgs = append(osArgs, args...)
|
||||
}
|
||||
}
|
||||
// handle the completion flag separately from the flagset since
|
||||
// completion could be attempted after a flag, but before its value was put
|
||||
// on the command line. this causes the flagset to interpret the completion
|
||||
// flag name as the value of the flag before it which is undesirable
|
||||
// note that we can only do this because the shell autocomplete function
|
||||
// always appends the completion flag at the end of the command
|
||||
tracef("checking osArgs %v (cmd=%[2]q)", osArgs, cmd.Name)
|
||||
cmd.shellCompletion, osArgs = checkShellCompleteFlag(cmd, osArgs)
|
||||
|
||||
tracef("setting cmd.shellCompletion=%[1]v from checkShellCompleteFlag (cmd=%[2]q)", cmd.shellCompletion && cmd.EnableShellCompletion, cmd.Name)
|
||||
cmd.shellCompletion = cmd.EnableShellCompletion && cmd.shellCompletion
|
||||
}
|
||||
|
||||
tracef("using post-checkShellCompleteFlag arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)
|
||||
|
||||
tracef("setting self as cmd in context (cmd=%[1]q)", cmd.Name)
|
||||
ctx = context.WithValue(ctx, commandContextKey, cmd)
|
||||
|
||||
if cmd.parent == nil {
|
||||
cmd.setupCommandGraph()
|
||||
}
|
||||
|
||||
var rargs Args = &stringSliceArgs{v: osArgs}
|
||||
var args Args = &stringSliceArgs{rargs.Tail()}
|
||||
|
||||
if cmd.isCompletionCommand {
|
||||
tracef("completion command detected, skipping pre-parse (cmd=%[1]q)", cmd.Name)
|
||||
cmd.parsedArgs = args
|
||||
return ctx, cmd.Action(ctx, cmd)
|
||||
}
|
||||
|
||||
for _, f := range cmd.allFlags() {
|
||||
if err := f.PreParse(); err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if cmd.SkipFlagParsing {
|
||||
tracef("skipping flag parsing (cmd=%[1]q)", cmd.Name)
|
||||
cmd.parsedArgs = args
|
||||
} else {
|
||||
cmd.parsedArgs, err = cmd.parseFlags(args)
|
||||
}
|
||||
|
||||
tracef("using post-parse arguments %[1]q (cmd=%[2]q)", args, cmd.Name)
|
||||
|
||||
if checkCompletions(ctx, cmd) {
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
tracef("setting deferErr from %[1]q (cmd=%[2]q)", err, cmd.Name)
|
||||
deferErr = err
|
||||
|
||||
cmd.isInError = true
|
||||
if cmd.OnUsageError != nil {
|
||||
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
|
||||
err = cmd.handleExitCoder(ctx, err)
|
||||
return ctx, err
|
||||
}
|
||||
fmt.Fprintf(cmd.Root().ErrWriter, "Incorrect Usage: %s\n\n", err.Error())
|
||||
if cmd.Suggest {
|
||||
if suggestion, err := cmd.suggestFlagFromError(err, ""); err == nil {
|
||||
fmt.Fprintf(cmd.Root().ErrWriter, "%s", suggestion)
|
||||
}
|
||||
}
|
||||
if !cmd.hideHelp() {
|
||||
if cmd.parent == nil {
|
||||
tracef("running ShowRootCommandHelp")
|
||||
if err := ShowRootCommandHelp(cmd); err != nil {
|
||||
tracef("SILENTLY IGNORING ERROR running ShowRootCommandHelp %[1]v (cmd=%[2]q)", err, cmd.Name)
|
||||
}
|
||||
} else {
|
||||
tracef("running ShowCommandHelp with %[1]q", cmd.Name)
|
||||
if err := ShowCommandHelp(ctx, cmd, cmd.Name); err != nil {
|
||||
tracef("SILENTLY IGNORING ERROR running ShowCommandHelp with %[1]q %[2]v", cmd.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
if cmd.checkHelp() {
|
||||
return ctx, helpCommandAction(ctx, cmd)
|
||||
} else {
|
||||
tracef("no help is wanted (cmd=%[1]q)", cmd.Name)
|
||||
}
|
||||
|
||||
if cmd.parent == nil && !cmd.HideVersion && checkVersion(cmd) {
|
||||
ShowVersion(cmd)
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
for _, flag := range cmd.allFlags() {
|
||||
cmd.setMultiValueParsingConfig(flag)
|
||||
isSet := flag.IsSet()
|
||||
if err := flag.PostParse(); err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
// add env set flags here
|
||||
if !isSet && flag.IsSet() {
|
||||
cmd.setFlags[flag] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if cmd.After != nil && !cmd.Root().shellCompletion {
|
||||
defer func() {
|
||||
if err := cmd.After(ctx, cmd); err != nil {
|
||||
err = cmd.handleExitCoder(ctx, err)
|
||||
|
||||
if deferErr != nil {
|
||||
deferErr = newMultiError(deferErr, err)
|
||||
} else {
|
||||
deferErr = err
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Walk the parent chain to check mutually exclusive flag groups
|
||||
// defined on ancestor commands, since persistent flags are inherited.
|
||||
for pCmd := cmd; pCmd != nil; pCmd = pCmd.parent {
|
||||
for _, grp := range pCmd.MutuallyExclusiveFlags {
|
||||
if err := grp.check(cmd); err != nil {
|
||||
if cmd.OnUsageError != nil {
|
||||
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
|
||||
} else {
|
||||
_ = ShowSubcommandHelp(cmd)
|
||||
}
|
||||
return ctx, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var subCmd *Command
|
||||
if cmd.parsedArgs.Present() {
|
||||
tracef("checking positional args %[1]q (cmd=%[2]q)", cmd.parsedArgs, cmd.Name)
|
||||
|
||||
name := cmd.parsedArgs.First()
|
||||
|
||||
tracef("using first positional argument as sub-command name=%[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
|
||||
if cmd.SuggestCommandFunc != nil && name != "--" {
|
||||
name = cmd.SuggestCommandFunc(cmd.Commands, name)
|
||||
tracef("suggested command name=%1[q] (cmd=%[2]q)", name, cmd.Name)
|
||||
}
|
||||
subCmd = cmd.Command(name)
|
||||
if subCmd == nil {
|
||||
hasDefault := cmd.DefaultCommand != ""
|
||||
isFlagName := slices.Contains(cmd.FlagNames(), name)
|
||||
|
||||
if hasDefault {
|
||||
tracef("using default command=%[1]q (cmd=%[2]q)", cmd.DefaultCommand, cmd.Name)
|
||||
}
|
||||
|
||||
if isFlagName || hasDefault {
|
||||
argsWithDefault := cmd.argsWithDefaultCommand(cmd.parsedArgs)
|
||||
tracef("using default command args=%[1]q (cmd=%[2]q)", argsWithDefault, cmd.Name)
|
||||
subCmd = cmd.Command(argsWithDefault.First())
|
||||
cmd.parsedArgs = argsWithDefault
|
||||
}
|
||||
}
|
||||
} else if cmd.DefaultCommand != "" {
|
||||
tracef("no positional args present; checking default command %[1]q (cmd=%[2]q)", cmd.DefaultCommand, cmd.Name)
|
||||
|
||||
if dc := cmd.Command(cmd.DefaultCommand); dc != cmd {
|
||||
subCmd = dc
|
||||
}
|
||||
}
|
||||
|
||||
// If a subcommand has been resolved, let it handle the remaining execution.
|
||||
if subCmd != nil {
|
||||
tracef("running sub-command %[1]q with arguments %[2]q (cmd=%[3]q)", subCmd.Name, cmd.Args(), cmd.Name)
|
||||
|
||||
// It is important that we overwrite the ctx variable in the current
|
||||
// function so any defer'd functions use the new context returned
|
||||
// from the sub command.
|
||||
ctx, err = subCmd.run(ctx, cmd.Args().Slice())
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
// This code path is the innermost command execution. Here we actually
|
||||
// perform the command action.
|
||||
//
|
||||
// First, resolve the chain of nested commands up to the parent.
|
||||
var cmdChain []*Command
|
||||
for p := cmd; p != nil; p = p.parent {
|
||||
cmdChain = append(cmdChain, p)
|
||||
}
|
||||
slices.Reverse(cmdChain)
|
||||
|
||||
// Run Before actions in order.
|
||||
for _, cmd := range cmdChain {
|
||||
if cmd.Before == nil {
|
||||
continue
|
||||
}
|
||||
if bctx, err := cmd.Before(ctx, cmd); err != nil {
|
||||
deferErr = cmd.handleExitCoder(ctx, err)
|
||||
return ctx, deferErr
|
||||
} else if bctx != nil {
|
||||
ctx = bctx
|
||||
}
|
||||
}
|
||||
|
||||
// Run flag actions in order.
|
||||
// These take a context, so this has to happen after Before actions.
|
||||
for _, cmd := range cmdChain {
|
||||
tracef("running flag actions (cmd=%[1]q)", cmd.Name)
|
||||
if err := cmd.runFlagActions(ctx); err != nil {
|
||||
deferErr = cmd.handleExitCoder(ctx, err)
|
||||
return ctx, deferErr
|
||||
}
|
||||
}
|
||||
|
||||
if err := cmd.checkAllRequiredFlags(); err != nil {
|
||||
cmd.isInError = true
|
||||
if cmd.OnUsageError != nil {
|
||||
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
|
||||
} else {
|
||||
_ = ShowSubcommandHelp(cmd)
|
||||
}
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
// Run the command action.
|
||||
if len(cmd.Arguments) > 0 {
|
||||
rargs := cmd.Args().Slice()
|
||||
tracef("calling argparse with %[1]v", rargs)
|
||||
for _, arg := range cmd.Arguments {
|
||||
var err error
|
||||
rargs, err = arg.Parse(rargs)
|
||||
if err != nil {
|
||||
tracef("calling with %[1]v (cmd=%[2]q)", err, cmd.Name)
|
||||
if cmd.OnUsageError != nil {
|
||||
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
|
||||
}
|
||||
err = cmd.handleExitCoder(ctx, err)
|
||||
return ctx, err
|
||||
}
|
||||
}
|
||||
cmd.parsedArgs = &stringSliceArgs{v: rargs}
|
||||
}
|
||||
|
||||
if err := cmd.Action(ctx, cmd); err != nil {
|
||||
tracef("calling handleExitCoder with %[1]v (cmd=%[2]q)", err, cmd.Name)
|
||||
deferErr = cmd.handleExitCoder(ctx, err)
|
||||
}
|
||||
|
||||
tracef("returning deferErr (cmd=%[1]q) %[2]q", cmd.Name, deferErr)
|
||||
return ctx, deferErr
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (cmd *Command) setupDefaults(osArgs []string) {
|
||||
if cmd.didSetupDefaults {
|
||||
tracef("already did setup (cmd=%[1]q)", cmd.Name)
|
||||
return
|
||||
}
|
||||
|
||||
cmd.didSetupDefaults = true
|
||||
|
||||
isRoot := cmd.parent == nil
|
||||
tracef("isRoot? %[1]v (cmd=%[2]q)", isRoot, cmd.Name)
|
||||
|
||||
if cmd.ShellComplete == nil {
|
||||
tracef("setting default ShellComplete (cmd=%[1]q)", cmd.Name)
|
||||
cmd.ShellComplete = DefaultCompleteWithFlags
|
||||
}
|
||||
|
||||
if cmd.Name == "" && isRoot {
|
||||
name := filepath.Base(osArgs[0])
|
||||
tracef("setting cmd.Name from first arg basename (cmd=%[1]q)", name)
|
||||
cmd.Name = name
|
||||
}
|
||||
|
||||
if cmd.Usage == "" && isRoot {
|
||||
tracef("setting default Usage (cmd=%[1]q)", cmd.Name)
|
||||
cmd.Usage = "A new cli application"
|
||||
}
|
||||
|
||||
if cmd.Version == "" {
|
||||
tracef("setting HideVersion=true due to empty Version (cmd=%[1]q)", cmd.Name)
|
||||
cmd.HideVersion = true
|
||||
}
|
||||
|
||||
if cmd.Action == nil {
|
||||
tracef("setting default Action as help command action (cmd=%[1]q)", cmd.Name)
|
||||
cmd.Action = helpCommandAction
|
||||
}
|
||||
|
||||
if cmd.Reader == nil {
|
||||
tracef("setting default Reader as os.Stdin (cmd=%[1]q)", cmd.Name)
|
||||
cmd.Reader = os.Stdin
|
||||
}
|
||||
|
||||
if cmd.Writer == nil {
|
||||
tracef("setting default Writer as os.Stdout (cmd=%[1]q)", cmd.Name)
|
||||
cmd.Writer = os.Stdout
|
||||
}
|
||||
|
||||
if cmd.ErrWriter == nil {
|
||||
tracef("setting default ErrWriter as os.Stderr (cmd=%[1]q)", cmd.Name)
|
||||
cmd.ErrWriter = os.Stderr
|
||||
}
|
||||
|
||||
if cmd.AllowExtFlags {
|
||||
tracef("visiting all flags given AllowExtFlags=true (cmd=%[1]q)", cmd.Name)
|
||||
// add global flags added by other packages
|
||||
flag.VisitAll(func(f *flag.Flag) {
|
||||
// skip test flags
|
||||
if !strings.HasPrefix(f.Name, ignoreFlagPrefix) {
|
||||
cmd.Flags = append(cmd.Flags, &extFlag{f})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for _, subCmd := range cmd.Commands {
|
||||
tracef("setting sub-command (cmd=%[1]q) parent as self (cmd=%[2]q)", subCmd.Name, cmd.Name)
|
||||
subCmd.parent = cmd
|
||||
}
|
||||
|
||||
cmd.ensureHelp()
|
||||
|
||||
if !cmd.HideVersion && isRoot {
|
||||
tracef("appending version flag (cmd=%[1]q)", cmd.Name)
|
||||
if !cmd.globaVersionFlagAdded {
|
||||
var localVersionFlag Flag
|
||||
if globalVersionFlag, ok := VersionFlag.(*BoolFlag); ok {
|
||||
flag := *globalVersionFlag
|
||||
localVersionFlag = &flag
|
||||
} else {
|
||||
localVersionFlag = VersionFlag
|
||||
}
|
||||
|
||||
cmd.appendFlag(localVersionFlag)
|
||||
cmd.globaVersionFlagAdded = true
|
||||
}
|
||||
}
|
||||
|
||||
if cmd.PrefixMatchCommands && cmd.SuggestCommandFunc == nil {
|
||||
tracef("setting default SuggestCommandFunc (cmd=%[1]q)", cmd.Name)
|
||||
cmd.SuggestCommandFunc = suggestCommand
|
||||
}
|
||||
|
||||
if isRoot && cmd.EnableShellCompletion || cmd.ConfigureShellCompletionCommand != nil {
|
||||
completionCommand := buildCompletionCommand(cmd.Name)
|
||||
|
||||
if cmd.ShellCompletionCommandName != "" {
|
||||
tracef(
|
||||
"setting completion command name (%[1]q) from "+
|
||||
"cmd.ShellCompletionCommandName (cmd=%[2]q)",
|
||||
cmd.ShellCompletionCommandName, cmd.Name,
|
||||
)
|
||||
completionCommand.Name = cmd.ShellCompletionCommandName
|
||||
}
|
||||
|
||||
tracef("appending completionCommand (cmd=%[1]q)", cmd.Name)
|
||||
cmd.appendCommand(completionCommand)
|
||||
if cmd.ConfigureShellCompletionCommand != nil {
|
||||
cmd.ConfigureShellCompletionCommand(completionCommand)
|
||||
}
|
||||
}
|
||||
|
||||
tracef("setting command categories (cmd=%[1]q)", cmd.Name)
|
||||
cmd.categories = newCommandCategories()
|
||||
|
||||
for _, subCmd := range cmd.Commands {
|
||||
cmd.categories.AddCommand(subCmd.Category, subCmd)
|
||||
}
|
||||
|
||||
tracef("sorting command categories (cmd=%[1]q)", cmd.Name)
|
||||
sort.Sort(cmd.categories.(*commandCategories))
|
||||
|
||||
tracef("setting category on mutually exclusive flags (cmd=%[1]q)", cmd.Name)
|
||||
for _, grp := range cmd.MutuallyExclusiveFlags {
|
||||
grp.propagateCategory()
|
||||
}
|
||||
|
||||
tracef("setting flag categories (cmd=%[1]q)", cmd.Name)
|
||||
cmd.flagCategories = newFlagCategoriesFromFlags(cmd.allFlags())
|
||||
|
||||
if cmd.Metadata == nil {
|
||||
tracef("setting default Metadata (cmd=%[1]q)", cmd.Name)
|
||||
cmd.Metadata = map[string]any{}
|
||||
}
|
||||
|
||||
cmd.setFlags = map[Flag]struct{}{}
|
||||
}
|
||||
|
||||
func (cmd *Command) setupCommandGraph() {
|
||||
tracef("setting up command graph (cmd=%[1]q)", cmd.Name)
|
||||
|
||||
for _, subCmd := range cmd.Commands {
|
||||
subCmd.parent = cmd
|
||||
subCmd.setupSubcommand()
|
||||
subCmd.setupCommandGraph()
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Command) setupSubcommand() {
|
||||
tracef("setting up self as sub-command (cmd=%[1]q)", cmd.Name)
|
||||
|
||||
cmd.ensureHelp()
|
||||
|
||||
tracef("setting command categories (cmd=%[1]q)", cmd.Name)
|
||||
cmd.categories = newCommandCategories()
|
||||
|
||||
for _, subCmd := range cmd.Commands {
|
||||
cmd.categories.AddCommand(subCmd.Category, subCmd)
|
||||
}
|
||||
|
||||
tracef("sorting command categories (cmd=%[1]q)", cmd.Name)
|
||||
sort.Sort(cmd.categories.(*commandCategories))
|
||||
|
||||
tracef("setting category on mutually exclusive flags (cmd=%[1]q)", cmd.Name)
|
||||
for _, grp := range cmd.MutuallyExclusiveFlags {
|
||||
grp.propagateCategory()
|
||||
}
|
||||
|
||||
tracef("setting flag categories (cmd=%[1]q)", cmd.Name)
|
||||
cmd.flagCategories = newFlagCategoriesFromFlags(cmd.allFlags())
|
||||
}
|
||||
|
||||
func (cmd *Command) hideHelp() bool {
|
||||
tracef("hide help (cmd=%[1]q)", cmd.Name)
|
||||
for c := cmd; c != nil; c = c.parent {
|
||||
if c.HideHelp {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd *Command) ensureHelp() {
|
||||
tracef("ensuring help (cmd=%[1]q)", cmd.Name)
|
||||
|
||||
helpCommand := buildHelpCommand(true)
|
||||
|
||||
if !cmd.hideHelp() {
|
||||
if cmd.Command(helpCommand.Name) == nil {
|
||||
if !cmd.HideHelpCommand {
|
||||
tracef("appending helpCommand (cmd=%[1]q)", cmd.Name)
|
||||
cmd.appendCommand(helpCommand)
|
||||
}
|
||||
}
|
||||
|
||||
if HelpFlag != nil {
|
||||
if !cmd.globaHelpFlagAdded {
|
||||
var localHelpFlag Flag
|
||||
if globalHelpFlag, ok := HelpFlag.(*BoolFlag); ok {
|
||||
flag := *globalHelpFlag
|
||||
localHelpFlag = &flag
|
||||
} else {
|
||||
localHelpFlag = HelpFlag
|
||||
}
|
||||
|
||||
tracef("appending HelpFlag (cmd=%[1]q)", cmd.Name)
|
||||
cmd.appendFlag(localHelpFlag)
|
||||
cmd.globaHelpFlagAdded = true
|
||||
} else {
|
||||
tracef("HelpFlag already added, skip (cmd=%[1]q)", cmd.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
completionCommandName = "completion"
|
||||
|
||||
// This flag is supposed to only be used by the completion script itself to generate completions on the fly.
|
||||
completionFlag = "--generate-shell-completion"
|
||||
)
|
||||
|
||||
type renderCompletion func(cmd *Command, appName string) (string, error)
|
||||
|
||||
var (
|
||||
//go:embed autocomplete
|
||||
autoCompleteFS embed.FS
|
||||
|
||||
shellCompletions = map[string]renderCompletion{
|
||||
"bash": func(c *Command, appName string) (string, error) {
|
||||
b, err := autoCompleteFS.ReadFile("autocomplete/bash_autocomplete")
|
||||
return fmt.Sprintf(string(b), appName), err
|
||||
},
|
||||
"zsh": func(c *Command, appName string) (string, error) {
|
||||
b, err := autoCompleteFS.ReadFile("autocomplete/zsh_autocomplete")
|
||||
return fmt.Sprintf(string(b), appName), err
|
||||
},
|
||||
"fish": func(c *Command, appName string) (string, error) {
|
||||
b, err := autoCompleteFS.ReadFile("autocomplete/fish_autocomplete")
|
||||
return fmt.Sprintf(string(b), appName), err
|
||||
},
|
||||
"pwsh": func(c *Command, appName string) (string, error) {
|
||||
b, err := autoCompleteFS.ReadFile("autocomplete/powershell_autocomplete.ps1")
|
||||
return string(b), err
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const completionDescription = `Output shell completion script for bash, zsh, fish, or Powershell.
|
||||
Source the output to enable completion.
|
||||
|
||||
# .bashrc
|
||||
source <($COMMAND completion bash)
|
||||
|
||||
# .zshrc
|
||||
source <($COMMAND completion zsh)
|
||||
|
||||
# fish
|
||||
$COMMAND completion fish > ~/.config/fish/completions/$COMMAND.fish
|
||||
|
||||
# Powershell
|
||||
Output the script to path/to/autocomplete/$COMMAND.ps1 an run it.
|
||||
`
|
||||
|
||||
func buildCompletionCommand(appName string) *Command {
|
||||
return &Command{
|
||||
Name: completionCommandName,
|
||||
Hidden: true,
|
||||
Usage: "Output shell completion script for bash, zsh, fish, or Powershell",
|
||||
Description: strings.ReplaceAll(completionDescription, "$COMMAND", appName),
|
||||
Action: func(ctx context.Context, cmd *Command) error {
|
||||
return printShellCompletion(ctx, cmd, appName)
|
||||
},
|
||||
isCompletionCommand: true,
|
||||
}
|
||||
}
|
||||
|
||||
func printShellCompletion(_ context.Context, cmd *Command, appName string) error {
|
||||
var shells []string
|
||||
for k := range shellCompletions {
|
||||
shells = append(shells, k)
|
||||
}
|
||||
|
||||
sort.Strings(shells)
|
||||
|
||||
if cmd.Args().Len() == 0 {
|
||||
return Exit(fmt.Sprintf("no shell provided for completion command. available shells are %+v", shells), 1)
|
||||
}
|
||||
s := cmd.Args().First()
|
||||
|
||||
renderCompletion, ok := shellCompletions[s]
|
||||
if !ok {
|
||||
return Exit(fmt.Sprintf("unknown shell %s, available shells are %+v", s, shells), 1)
|
||||
}
|
||||
|
||||
completionScript, err := renderCompletion(cmd, appName)
|
||||
if err != nil {
|
||||
return Exit(err, 1)
|
||||
}
|
||||
|
||||
_, err = cmd.Writer.Write([]byte(completionScript))
|
||||
if err != nil {
|
||||
return Exit(err, 1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func prefixFor(name string) (prefix string) {
|
||||
if len(name) == 1 {
|
||||
prefix = "-"
|
||||
} else {
|
||||
prefix = "--"
|
||||
}
|
||||
|
||||
return prefix
|
||||
}
|
||||
|
||||
// Returns the placeholder, if any, and the unquoted usage string.
|
||||
func unquoteUsage(usage string) (string, string) {
|
||||
for i := 0; i < len(usage); i++ {
|
||||
if usage[i] == '`' {
|
||||
for j := i + 1; j < len(usage); j++ {
|
||||
if usage[j] == '`' {
|
||||
name := usage[i+1 : j]
|
||||
usage = usage[:i] + name + usage[j+1:]
|
||||
return name, usage
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return "", usage
|
||||
}
|
||||
|
||||
func prefixedNames(names []string, placeholder string) string {
|
||||
var prefixed strings.Builder
|
||||
for i, name := range names {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
prefixed.WriteString(prefixFor(name) + name)
|
||||
if placeholder != "" {
|
||||
prefixed.WriteString(" " + placeholder)
|
||||
}
|
||||
if i < len(names)-1 {
|
||||
prefixed.WriteString(", ")
|
||||
}
|
||||
}
|
||||
return prefixed.String()
|
||||
}
|
||||
|
||||
func envFormat(envVars []string, prefix, sep, suffix string) string {
|
||||
if len(envVars) > 0 {
|
||||
return fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(envVars, sep), suffix)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func defaultEnvFormat(envVars []string) string {
|
||||
return envFormat(envVars, "$", ", $", "")
|
||||
}
|
||||
|
||||
func withEnvHint(envVars []string, str string) string {
|
||||
envText := ""
|
||||
if runtime.GOOS != "windows" || os.Getenv("PSHOME") != "" {
|
||||
envText = defaultEnvFormat(envVars)
|
||||
} else {
|
||||
envText = envFormat(envVars, "%", "%, %", "%")
|
||||
}
|
||||
return str + envText
|
||||
}
|
||||
|
||||
func withFileHint(filePath, str string) string {
|
||||
fileText := ""
|
||||
if filePath != "" {
|
||||
fileText = fmt.Sprintf(" [%s]", filePath)
|
||||
}
|
||||
return str + fileText
|
||||
}
|
||||
|
||||
func formatDefault(format string) string {
|
||||
return " (default: " + format + ")"
|
||||
}
|
||||
|
||||
func stringifyFlag(f Flag) string {
|
||||
// enforce DocGeneration interface on flags to avoid reflection
|
||||
df, ok := f.(DocGenerationFlag)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
placeholder, usage := unquoteUsage(df.GetUsage())
|
||||
needsPlaceholder := df.TakesValue()
|
||||
// if needsPlaceholder is true, placeholder is empty
|
||||
if needsPlaceholder && placeholder == "" {
|
||||
// try to get type from flag
|
||||
if tname := df.TypeName(); tname != "" {
|
||||
placeholder = tname
|
||||
} else {
|
||||
placeholder = defaultPlaceholder
|
||||
}
|
||||
}
|
||||
|
||||
defaultValueString := ""
|
||||
|
||||
// don't print default text for required flags
|
||||
if rf, ok := f.(RequiredFlag); !ok || !rf.IsRequired() {
|
||||
if df.IsDefaultVisible() {
|
||||
if s := df.GetDefaultText(); s != "" {
|
||||
defaultValueString = fmt.Sprintf(formatDefault("%s"), s)
|
||||
} else if df.TakesValue() && df.GetValue() != "" {
|
||||
defaultValueString = fmt.Sprintf(formatDefault("%s"), df.GetValue())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
usageWithDefault := strings.TrimSpace(usage + defaultValueString)
|
||||
|
||||
pn := prefixedNames(f.Names(), placeholder)
|
||||
sliceFlag, ok := f.(DocGenerationMultiValueFlag)
|
||||
if ok && sliceFlag.IsMultiValueFlag() {
|
||||
pn = pn + " [ " + pn + " ]"
|
||||
}
|
||||
|
||||
return withEnvHint(df.GetEnvVars(), fmt.Sprintf("%s\t%s", pn, usageWithDefault))
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// OsExiter is the function used when the app exits. If not set defaults to os.Exit.
|
||||
var OsExiter = os.Exit
|
||||
|
||||
// ErrWriter is used to write errors to the user. This can be anything
|
||||
// implementing the io.Writer interface and defaults to os.Stderr.
|
||||
var ErrWriter io.Writer = os.Stderr
|
||||
|
||||
// MultiError is an error that wraps multiple errors.
|
||||
type MultiError interface {
|
||||
error
|
||||
Errors() []error
|
||||
}
|
||||
|
||||
// newMultiError creates a new MultiError. Pass in one or more errors.
|
||||
func newMultiError(err ...error) MultiError {
|
||||
ret := multiError(err)
|
||||
return &ret
|
||||
}
|
||||
|
||||
type multiError []error
|
||||
|
||||
// Error implements the error interface.
|
||||
func (m *multiError) Error() string {
|
||||
errs := make([]string, len(*m))
|
||||
for i, err := range *m {
|
||||
errs[i] = err.Error()
|
||||
}
|
||||
|
||||
return strings.Join(errs, "\n")
|
||||
}
|
||||
|
||||
// Errors returns a copy of the errors slice
|
||||
func (m *multiError) Errors() []error {
|
||||
errs := make([]error, len(*m))
|
||||
copy(errs, *m)
|
||||
return errs
|
||||
}
|
||||
|
||||
type requiredFlagsErr interface {
|
||||
error
|
||||
}
|
||||
|
||||
type errRequiredFlags struct {
|
||||
missingFlags []string
|
||||
}
|
||||
|
||||
func (e *errRequiredFlags) Error() string {
|
||||
if len(e.missingFlags) == 1 {
|
||||
return fmt.Sprintf("Required flag %q not set", e.missingFlags[0])
|
||||
}
|
||||
joinedMissingFlags := strings.Join(e.missingFlags, ", ")
|
||||
return fmt.Sprintf("Required flags %q not set", joinedMissingFlags)
|
||||
}
|
||||
|
||||
type mutuallyExclusiveGroup struct {
|
||||
flag1Name string
|
||||
flag2Name string
|
||||
}
|
||||
|
||||
func (e *mutuallyExclusiveGroup) Error() string {
|
||||
return fmt.Sprintf("option %s cannot be set along with option %s", e.flag1Name, e.flag2Name)
|
||||
}
|
||||
|
||||
type mutuallyExclusiveGroupRequiredFlag struct {
|
||||
flags *MutuallyExclusiveFlags
|
||||
}
|
||||
|
||||
func (e *mutuallyExclusiveGroupRequiredFlag) Error() string {
|
||||
var missingFlags []string
|
||||
for _, grpf := range e.flags.Flags {
|
||||
var grpString []string
|
||||
for _, f := range grpf {
|
||||
grpString = append(grpString, f.Names()...)
|
||||
}
|
||||
missingFlags = append(missingFlags, strings.Join(grpString, " "))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("one of these flags needs to be provided: %s", strings.Join(missingFlags, ", "))
|
||||
}
|
||||
|
||||
// ErrorFormatter is the interface that will suitably format the error output
|
||||
type ErrorFormatter interface {
|
||||
Format(s fmt.State, verb rune)
|
||||
}
|
||||
|
||||
// ExitCoder is the interface checked by `Command` for a custom exit code.
|
||||
type ExitCoder interface {
|
||||
error
|
||||
ExitCode() int
|
||||
}
|
||||
|
||||
type exitError struct {
|
||||
exitCode int
|
||||
err error
|
||||
}
|
||||
|
||||
// Exit wraps a message and exit code into an error, which by default is
|
||||
// handled with a call to os.Exit during default error handling.
|
||||
//
|
||||
// This is the simplest way to trigger a non-zero exit code for a Command without
|
||||
// having to call os.Exit manually. During testing, this behavior can be avoided
|
||||
// by overriding the ExitErrHandler function on a Command or the package-global
|
||||
// OsExiter function.
|
||||
func Exit(message any, exitCode int) ExitCoder {
|
||||
var err error
|
||||
|
||||
switch e := message.(type) {
|
||||
case ErrorFormatter:
|
||||
err = fmt.Errorf("%+v", message)
|
||||
case error:
|
||||
err = e
|
||||
default:
|
||||
err = fmt.Errorf("%+v", message)
|
||||
}
|
||||
|
||||
return &exitError{
|
||||
err: err,
|
||||
exitCode: exitCode,
|
||||
}
|
||||
}
|
||||
|
||||
func (ee *exitError) Error() string {
|
||||
return ee.err.Error()
|
||||
}
|
||||
|
||||
func (ee *exitError) ExitCode() int {
|
||||
return ee.exitCode
|
||||
}
|
||||
|
||||
// HandleExitCoder handles errors implementing ExitCoder by printing their
|
||||
// message and calling OsExiter with the given exit code.
|
||||
//
|
||||
// If the given error instead implements MultiError, each error will be checked
|
||||
// for the ExitCoder interface, and OsExiter will be called with the last exit
|
||||
// code found, or exit code 1 if no ExitCoder is found.
|
||||
//
|
||||
// This function is the default error-handling behavior for a Command.
|
||||
func HandleExitCoder(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if exitErr, ok := err.(ExitCoder); ok {
|
||||
if msg := err.Error(); msg != "" {
|
||||
if _, ok := exitErr.(ErrorFormatter); ok {
|
||||
_, _ = fmt.Fprintf(ErrWriter, "%+v\n", err)
|
||||
} else {
|
||||
_, _ = fmt.Fprintln(ErrWriter, err)
|
||||
}
|
||||
}
|
||||
OsExiter(exitErr.ExitCode())
|
||||
return
|
||||
}
|
||||
|
||||
if multiErr, ok := err.(MultiError); ok {
|
||||
code := handleMultiError(multiErr)
|
||||
OsExiter(code)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func handleMultiError(multiErr MultiError) int {
|
||||
code := 1
|
||||
for _, merr := range multiErr.Errors() {
|
||||
if multiErr2, ok := merr.(MultiError); ok {
|
||||
code = handleMultiError(multiErr2)
|
||||
} else if merr != nil {
|
||||
fmt.Fprintln(ErrWriter, merr)
|
||||
if exitErr, ok := merr.(ExitCoder); ok {
|
||||
code = exitErr.ExitCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// ToFishCompletion creates a fish completion string for the `*Command`
|
||||
// The function errors if either parsing or writing of the string fails.
|
||||
func (cmd *Command) ToFishCompletion() (string, error) {
|
||||
var w bytes.Buffer
|
||||
if err := cmd.writeFishCompletionTemplate(&w); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return w.String(), nil
|
||||
}
|
||||
|
||||
type fishCommandCompletionTemplate struct {
|
||||
Command *Command
|
||||
Completions []string
|
||||
AllCommands []string
|
||||
}
|
||||
|
||||
func (cmd *Command) writeFishCompletionTemplate(w io.Writer) error {
|
||||
const name = "cli"
|
||||
t, err := template.New(name).Parse(FishCompletionTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add global flags
|
||||
completions := prepareFishFlags(cmd.Name, cmd)
|
||||
|
||||
if cmd.ShellComplete != nil {
|
||||
var completion strings.Builder
|
||||
fmt.Fprintf(&completion,
|
||||
"complete -c %s -n '%s' -xa '(%s %s 2>/dev/null)'",
|
||||
cmd.Name,
|
||||
fishFlagHelper(cmd.Name, cmd),
|
||||
cmd.Name,
|
||||
completionFlag,
|
||||
)
|
||||
completions = append(completions, completion.String())
|
||||
}
|
||||
|
||||
// Add commands and their flags
|
||||
completions = append(
|
||||
completions,
|
||||
prepareFishCommands(cmd.Name, cmd)...,
|
||||
)
|
||||
|
||||
toplevelCommandNames := []string{}
|
||||
for _, child := range cmd.Commands {
|
||||
toplevelCommandNames = append(toplevelCommandNames, child.Names()...)
|
||||
}
|
||||
|
||||
return t.ExecuteTemplate(w, name, &fishCommandCompletionTemplate{
|
||||
Command: cmd,
|
||||
Completions: completions,
|
||||
AllCommands: toplevelCommandNames,
|
||||
})
|
||||
}
|
||||
|
||||
func prepareFishCommands(binary string, parent *Command) []string {
|
||||
commands := parent.Commands
|
||||
completions := []string{}
|
||||
for _, command := range commands {
|
||||
if !command.Hidden {
|
||||
var completion strings.Builder
|
||||
fmt.Fprintf(&completion,
|
||||
"complete -x -c %s -n '%s' -a '%s'",
|
||||
binary,
|
||||
fishSubcommandHelper(binary, parent, commands),
|
||||
command.Name,
|
||||
)
|
||||
|
||||
if command.Usage != "" {
|
||||
fmt.Fprintf(&completion,
|
||||
" -d '%s'",
|
||||
escapeSingleQuotes(command.Usage))
|
||||
}
|
||||
completions = append(completions, completion.String())
|
||||
}
|
||||
|
||||
if command.ShellComplete != nil {
|
||||
var completion strings.Builder
|
||||
var path []string
|
||||
lineage := command.Lineage()
|
||||
for i := len(lineage) - 2; i >= 0; i-- {
|
||||
path = append(path, lineage[i].Name)
|
||||
}
|
||||
|
||||
fmt.Fprintf(&completion,
|
||||
"complete -c %s -n '%s' -xa '(%s %s %s 2>/dev/null)'",
|
||||
binary,
|
||||
fishFlagHelper(binary, command),
|
||||
binary,
|
||||
strings.Join(path, " "),
|
||||
completionFlag,
|
||||
)
|
||||
completions = append(completions, completion.String())
|
||||
}
|
||||
|
||||
completions = append(
|
||||
completions,
|
||||
prepareFishFlags(binary, command)...,
|
||||
)
|
||||
|
||||
// recursively iterate subcommands
|
||||
completions = append(
|
||||
completions,
|
||||
prepareFishCommands(binary, command)...,
|
||||
)
|
||||
}
|
||||
|
||||
return completions
|
||||
}
|
||||
|
||||
func prepareFishFlags(binary string, owner *Command) []string {
|
||||
flags := owner.VisibleFlags()
|
||||
completions := []string{}
|
||||
for _, f := range flags {
|
||||
completion := &strings.Builder{}
|
||||
fmt.Fprintf(completion,
|
||||
"complete -c %s -n '%s'",
|
||||
binary,
|
||||
fishFlagHelper(binary, owner),
|
||||
)
|
||||
|
||||
fishAddFileFlag(f, completion)
|
||||
|
||||
for idx, opt := range f.Names() {
|
||||
if idx == 0 {
|
||||
fmt.Fprintf(completion,
|
||||
" -l %s", strings.TrimSpace(opt),
|
||||
)
|
||||
} else {
|
||||
fmt.Fprintf(completion,
|
||||
" -s %s", strings.TrimSpace(opt),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if flag, ok := f.(DocGenerationFlag); ok {
|
||||
if flag.TakesValue() {
|
||||
completion.WriteString(" -r")
|
||||
}
|
||||
|
||||
if flag.GetUsage() != "" {
|
||||
fmt.Fprintf(completion,
|
||||
" -d '%s'",
|
||||
escapeSingleQuotes(flag.GetUsage()))
|
||||
}
|
||||
}
|
||||
|
||||
completions = append(completions, completion.String())
|
||||
}
|
||||
|
||||
return completions
|
||||
}
|
||||
|
||||
func fishAddFileFlag(flag Flag, completion *strings.Builder) {
|
||||
switch f := flag.(type) {
|
||||
case *StringFlag:
|
||||
if f.TakesFile {
|
||||
return
|
||||
}
|
||||
case *StringSliceFlag:
|
||||
if f.TakesFile {
|
||||
return
|
||||
}
|
||||
}
|
||||
completion.WriteString(" -f")
|
||||
}
|
||||
|
||||
func fishSubcommandHelper(binary string, command *Command, siblings []*Command) string {
|
||||
fishHelper := fmt.Sprintf("__fish_%s_no_subcommand", binary)
|
||||
if len(command.Lineage()) > 1 {
|
||||
var siblingNames []string
|
||||
for _, sibling := range siblings {
|
||||
siblingNames = append(siblingNames, sibling.Names()...)
|
||||
}
|
||||
ancestry := commandAncestry(command)
|
||||
fishHelper = fmt.Sprintf(
|
||||
"%s; and not __fish_seen_subcommand_from %s",
|
||||
ancestry,
|
||||
strings.Join(siblingNames, " "),
|
||||
)
|
||||
}
|
||||
return fishHelper
|
||||
}
|
||||
|
||||
func fishFlagHelper(binary string, command *Command) string {
|
||||
fishHelper := fmt.Sprintf("__fish_%s_no_subcommand", binary)
|
||||
if len(command.Lineage()) > 1 {
|
||||
fishHelper = commandAncestry(command)
|
||||
}
|
||||
return fishHelper
|
||||
}
|
||||
|
||||
func commandAncestry(command *Command) string {
|
||||
var ancestry []string
|
||||
ancestors := command.Lineage()
|
||||
for i := len(ancestors) - 2; i >= 0; i-- {
|
||||
ancestry = append(
|
||||
ancestry,
|
||||
fmt.Sprintf(
|
||||
"__fish_seen_subcommand_from %s",
|
||||
strings.Join(ancestors[i].Names(), " "),
|
||||
),
|
||||
)
|
||||
}
|
||||
return strings.Join(ancestry, "; and ")
|
||||
}
|
||||
|
||||
func escapeSingleQuotes(input string) string {
|
||||
return strings.ReplaceAll(input, `'`, `\'`)
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const defaultPlaceholder = "value"
|
||||
|
||||
const (
|
||||
defaultSliceFlagSeparator = ","
|
||||
defaultMapFlagKeyValueSeparator = "="
|
||||
disableSliceFlagSeparator = false
|
||||
)
|
||||
|
||||
var slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano())
|
||||
|
||||
// GenerateShellCompletionFlag enables shell completion
|
||||
var GenerateShellCompletionFlag Flag = &BoolFlag{
|
||||
Name: "generate-shell-completion",
|
||||
Hidden: true,
|
||||
}
|
||||
|
||||
// VersionFlag prints the version for the application
|
||||
var VersionFlag Flag = &BoolFlag{
|
||||
Name: "version",
|
||||
Aliases: []string{"v"},
|
||||
Usage: "print the version",
|
||||
HideDefault: true,
|
||||
Local: true,
|
||||
}
|
||||
|
||||
// HelpFlag prints the help for all commands and subcommands.
|
||||
// Set to nil to disable the flag. The subcommand
|
||||
// will still be added unless HideHelp or HideHelpCommand is set to true.
|
||||
var HelpFlag Flag = &BoolFlag{
|
||||
Name: "help",
|
||||
Aliases: []string{"h"},
|
||||
Usage: "show help",
|
||||
HideDefault: true,
|
||||
Local: true,
|
||||
}
|
||||
|
||||
// FlagStringer converts a flag definition to a string. This is used by help
|
||||
// to display a flag.
|
||||
var FlagStringer FlagStringFunc = stringifyFlag
|
||||
|
||||
// Serializer is used to circumvent the limitations of flag.FlagSet.Set
|
||||
type Serializer interface {
|
||||
Serialize() string
|
||||
}
|
||||
|
||||
// FlagNamePrefixer converts a full flag name and its placeholder into the help
|
||||
// message flag prefix. This is used by the default FlagStringer.
|
||||
var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames
|
||||
|
||||
// FlagEnvHinter annotates flag help message with the environment variable
|
||||
// details. This is used by the default FlagStringer.
|
||||
var FlagEnvHinter FlagEnvHintFunc = withEnvHint
|
||||
|
||||
// FlagFileHinter annotates flag help message with the environment variable
|
||||
// details. This is used by the default FlagStringer.
|
||||
var FlagFileHinter FlagFileHintFunc = withFileHint
|
||||
|
||||
// FlagsByName is a slice of Flag.
|
||||
type FlagsByName []Flag
|
||||
|
||||
func (f FlagsByName) Len() int {
|
||||
return len(f)
|
||||
}
|
||||
|
||||
func (f FlagsByName) Less(i, j int) bool {
|
||||
return lexicographicLess(f[i].Names()[0], f[j].Names()[0])
|
||||
}
|
||||
|
||||
func (f FlagsByName) Swap(i, j int) {
|
||||
f[i], f[j] = f[j], f[i]
|
||||
}
|
||||
|
||||
// ActionableFlag is an interface that wraps Flag interface and RunAction operation.
|
||||
type ActionableFlag interface {
|
||||
RunAction(context.Context, *Command) error
|
||||
}
|
||||
|
||||
// Flag is a common interface related to parsing flags in cli.
|
||||
// For more advanced flag parsing techniques, it is recommended that
|
||||
// this interface be implemented.
|
||||
type Flag interface {
|
||||
fmt.Stringer
|
||||
|
||||
// Retrieve the value of the Flag
|
||||
Get() any
|
||||
|
||||
// Lifecycle methods.
|
||||
// flag callback prior to parsing
|
||||
PreParse() error
|
||||
|
||||
// flag callback post parsing
|
||||
PostParse() error
|
||||
|
||||
// Apply Flag settings to the given flag set
|
||||
Set(string, string) error
|
||||
|
||||
// All possible names for this flag
|
||||
Names() []string
|
||||
|
||||
// Whether the flag has been set or not
|
||||
IsSet() bool
|
||||
}
|
||||
|
||||
// RequiredFlag is an interface that allows us to mark flags as required
|
||||
// it allows flags required flags to be backwards compatible with the Flag interface
|
||||
type RequiredFlag interface {
|
||||
// whether the flag is a required flag or not
|
||||
IsRequired() bool
|
||||
}
|
||||
|
||||
// DocGenerationFlag is an interface that allows documentation generation for the flag
|
||||
type DocGenerationFlag interface {
|
||||
// TakesValue returns true if the flag takes a value, otherwise false
|
||||
TakesValue() bool
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
GetUsage() string
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
GetValue() string
|
||||
|
||||
// GetDefaultText returns the default text for this flag
|
||||
GetDefaultText() string
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
GetEnvVars() []string
|
||||
|
||||
// IsDefaultVisible returns whether the default value should be shown in
|
||||
// help text
|
||||
IsDefaultVisible() bool
|
||||
// TypeName to detect if a flag is a string, bool, etc.
|
||||
TypeName() string
|
||||
}
|
||||
|
||||
// DocGenerationMultiValueFlag extends DocGenerationFlag for slice/map based flags.
|
||||
type DocGenerationMultiValueFlag interface {
|
||||
DocGenerationFlag
|
||||
|
||||
// IsMultiValueFlag returns true for flags that can be given multiple times.
|
||||
IsMultiValueFlag() bool
|
||||
}
|
||||
|
||||
// Countable is an interface to enable detection of flag values which support
|
||||
// repetitive flags
|
||||
type Countable interface {
|
||||
Count() int
|
||||
}
|
||||
|
||||
// VisibleFlag is an interface that allows to check if a flag is visible
|
||||
type VisibleFlag interface {
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
IsVisible() bool
|
||||
}
|
||||
|
||||
// CategorizableFlag is an interface that allows us to potentially
|
||||
// use a flag in a categorized representation.
|
||||
type CategorizableFlag interface {
|
||||
// Returns the category of the flag
|
||||
GetCategory() string
|
||||
|
||||
// Sets the category of the flag
|
||||
SetCategory(string)
|
||||
}
|
||||
|
||||
// LocalFlag is an interface to enable detection of flags which are local
|
||||
// to current command
|
||||
type LocalFlag interface {
|
||||
IsLocal() bool
|
||||
}
|
||||
|
||||
func visibleFlags(fl []Flag) []Flag {
|
||||
var visible []Flag
|
||||
for _, f := range fl {
|
||||
if vf, ok := f.(VisibleFlag); ok && vf.IsVisible() {
|
||||
visible = append(visible, f)
|
||||
}
|
||||
}
|
||||
return visible
|
||||
}
|
||||
|
||||
func FlagNames(name string, aliases []string) []string {
|
||||
var ret []string
|
||||
|
||||
for _, part := range append([]string{name}, aliases...) {
|
||||
// v1 -> v2 migration warning zone:
|
||||
// Strip off anything after the first found comma or space, which
|
||||
// *hopefully* makes it a tiny bit more obvious that unexpected behavior is
|
||||
// caused by using the v1 form of stringly typed "Name".
|
||||
if i := strings.IndexAny(part, ", "); i >= 0 {
|
||||
ret = append(ret, part[:i])
|
||||
} else {
|
||||
ret = append(ret, part)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func hasFlag(flags []Flag, fl Flag) bool {
|
||||
return slices.Contains(flags, fl)
|
||||
}
|
||||
|
||||
func flagSplitMultiValues(val string, sliceSeparator string, disableSliceSeparator bool) []string {
|
||||
if disableSliceSeparator {
|
||||
return []string{val}
|
||||
}
|
||||
|
||||
if len(sliceSeparator) == 0 {
|
||||
sliceSeparator = defaultSliceFlagSeparator
|
||||
}
|
||||
return strings.Split(val, sliceSeparator)
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type BoolFlag = FlagBase[bool, BoolConfig, boolValue]
|
||||
|
||||
// BoolConfig defines the configuration for bool flags
|
||||
type BoolConfig struct {
|
||||
Count *int
|
||||
}
|
||||
|
||||
// boolValue needs to implement the boolFlag internal interface in flag
|
||||
// to be able to capture bool fields and values
|
||||
//
|
||||
// type boolFlag interface {
|
||||
// Value
|
||||
// IsBoolFlag() bool
|
||||
// }
|
||||
type boolValue struct {
|
||||
destination *bool
|
||||
count *int
|
||||
}
|
||||
|
||||
func (cmd *Command) Bool(name string) bool {
|
||||
if v, ok := cmd.Value(name).(bool); ok {
|
||||
tracef("bool available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("bool NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
// Create creates the bool value
|
||||
func (b boolValue) Create(val bool, p *bool, c BoolConfig) Value {
|
||||
*p = val
|
||||
if c.Count == nil {
|
||||
c.Count = new(int)
|
||||
}
|
||||
return &boolValue{
|
||||
destination: p,
|
||||
count: c.Count,
|
||||
}
|
||||
}
|
||||
|
||||
// ToString formats the bool value
|
||||
func (b boolValue) ToString(value bool) string {
|
||||
b.destination = &value
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (b *boolValue) Set(s string) error {
|
||||
v, err := strconv.ParseBool(s)
|
||||
if err != nil {
|
||||
err = errors.New("parse error")
|
||||
return err
|
||||
}
|
||||
*b.destination = v
|
||||
if b.count != nil {
|
||||
*b.count = *b.count + 1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *boolValue) Get() any { return *b.destination }
|
||||
|
||||
func (b *boolValue) String() string {
|
||||
return strconv.FormatBool(*b.destination)
|
||||
}
|
||||
|
||||
func (b *boolValue) IsBoolFlag() bool { return true }
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var DefaultInverseBoolPrefix = "no-"
|
||||
|
||||
type BoolWithInverseFlag struct {
|
||||
Name string `json:"name"` // name of the flag
|
||||
Category string `json:"category"` // category of the flag, if any
|
||||
DefaultText string `json:"defaultText"` // default text of the flag for usage purposes
|
||||
HideDefault bool `json:"hideDefault"` // whether to hide the default value in output
|
||||
Usage string `json:"usage"` // usage string for help output
|
||||
Sources ValueSourceChain `json:"-"` // sources to load flag value from
|
||||
Required bool `json:"required"` // whether the flag is required or not
|
||||
Hidden bool `json:"hidden"` // whether to hide the flag in help output
|
||||
Local bool `json:"local"` // whether the flag needs to be applied to subcommands as well
|
||||
Value bool `json:"defaultValue"` // default value for this flag if not set by from any source
|
||||
Destination *bool `json:"-"` // destination pointer for value when set
|
||||
Aliases []string `json:"aliases"` // Aliases that are allowed for this flag
|
||||
TakesFile bool `json:"takesFileArg"` // whether this flag takes a file argument, mainly for shell completion purposes
|
||||
Action func(context.Context, *Command, bool) error `json:"-"` // Action callback to be called when flag is set
|
||||
OnlyOnce bool `json:"onlyOnce"` // whether this flag can be duplicated on the command line
|
||||
Validator func(bool) error `json:"-"` // custom function to validate this flag value
|
||||
ValidateDefaults bool `json:"validateDefaults"` // whether to validate defaults or not
|
||||
Config BoolConfig `json:"config"` // Additional/Custom configuration associated with this flag type
|
||||
InversePrefix string `json:"invPrefix"` // The prefix used to indicate a negative value. Default: `env` becomes `no-env`
|
||||
|
||||
// unexported fields for internal use
|
||||
count int // number of times the flag has been set
|
||||
hasBeenSet bool // whether the flag has been set from env or file
|
||||
applied bool // whether the flag has been applied to a flag set already
|
||||
value Value // value representing this flag's value
|
||||
pset bool
|
||||
nset bool
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) IsSet() bool {
|
||||
return bif.hasBeenSet
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) Get() any {
|
||||
return bif.value.Get()
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) RunAction(ctx context.Context, cmd *Command) error {
|
||||
if bif.Action != nil {
|
||||
return bif.Action(ctx, cmd, bif.Get().(bool))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) IsLocal() bool {
|
||||
return bif.Local
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) inversePrefix() string {
|
||||
if bif.InversePrefix == "" {
|
||||
bif.InversePrefix = DefaultInverseBoolPrefix
|
||||
}
|
||||
|
||||
return bif.InversePrefix
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) PreParse() error {
|
||||
count := bif.Config.Count
|
||||
if count == nil {
|
||||
count = &bif.count
|
||||
}
|
||||
dest := bif.Destination
|
||||
if dest == nil {
|
||||
dest = new(bool)
|
||||
}
|
||||
*dest = bif.Value
|
||||
bif.value = &boolValue{
|
||||
destination: dest,
|
||||
count: count,
|
||||
}
|
||||
|
||||
// Validate the given default or values set from external sources as well
|
||||
if bif.Validator != nil && bif.ValidateDefaults {
|
||||
if err := bif.Validator(bif.value.Get().(bool)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
bif.applied = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) PostParse() error {
|
||||
tracef("postparse (flag=%[1]q)", bif.Name)
|
||||
|
||||
if !bif.hasBeenSet {
|
||||
if val, source, found := bif.Sources.LookupWithSource(); found {
|
||||
if val == "" {
|
||||
val = "false"
|
||||
}
|
||||
if err := bif.Set(bif.Name, val); err != nil {
|
||||
return fmt.Errorf(
|
||||
"could not parse %[1]q as %[2]T value from %[3]s for flag %[4]s: %[5]s",
|
||||
val, bif.Value, source, bif.Name, err,
|
||||
)
|
||||
}
|
||||
|
||||
bif.hasBeenSet = true
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) Set(name, val string) error {
|
||||
if bif.count > 0 && bif.OnlyOnce {
|
||||
return fmt.Errorf("cant duplicate this flag")
|
||||
}
|
||||
|
||||
bif.hasBeenSet = true
|
||||
|
||||
if slices.Contains(append([]string{bif.Name}, bif.Aliases...), name) {
|
||||
if bif.nset {
|
||||
return fmt.Errorf("cannot set both flags `--%s` and `--%s`", bif.Name, bif.inversePrefix()+bif.Name)
|
||||
}
|
||||
if err := bif.value.Set(val); err != nil {
|
||||
return err
|
||||
}
|
||||
bif.pset = true
|
||||
} else {
|
||||
if bif.pset {
|
||||
return fmt.Errorf("cannot set both flags `--%s` and `--%s`", bif.Name, bif.inversePrefix()+bif.Name)
|
||||
}
|
||||
if err := bif.value.Set("false"); err != nil {
|
||||
return err
|
||||
}
|
||||
bif.nset = true
|
||||
}
|
||||
|
||||
if bif.Validator != nil {
|
||||
return bif.Validator(bif.value.Get().(bool))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) Names() []string {
|
||||
names := append([]string{bif.Name}, bif.Aliases...)
|
||||
|
||||
for _, name := range names {
|
||||
names = append(names, bif.inversePrefix()+name)
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) IsRequired() bool {
|
||||
return bif.Required
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) IsVisible() bool {
|
||||
return !bif.Hidden
|
||||
}
|
||||
|
||||
// String implements the standard Stringer interface.
|
||||
//
|
||||
// Example for BoolFlag{Name: "env"}
|
||||
// --[no-]env (default: false)
|
||||
//
|
||||
// Example for BoolFlag{Name: "env", Aliases: []string{"e"}}
|
||||
// --[no-]env, -e (default: false)
|
||||
func (bif *BoolWithInverseFlag) String() string {
|
||||
out := FlagStringer(bif)
|
||||
|
||||
i := strings.Index(out, "\t")
|
||||
|
||||
prefix := "--"
|
||||
|
||||
// single character flags are prefixed with `-` instead of `--`
|
||||
if len(bif.Name) == 1 {
|
||||
prefix = "-"
|
||||
}
|
||||
|
||||
// Guard against a FlagStringer that returns a string without a tab (e.g.
|
||||
// a custom stringer or the default stringer when the flag does not
|
||||
// implement DocGenerationFlag). In that case treat the entire output as
|
||||
// the tab-delimited suffix so the slice never goes out of bounds.
|
||||
if i < 0 {
|
||||
i = 0
|
||||
}
|
||||
|
||||
var aliasParts []string
|
||||
for _, alias := range bif.Aliases {
|
||||
aPrefix := "--"
|
||||
if len(alias) == 1 {
|
||||
aPrefix = "-"
|
||||
}
|
||||
aliasParts = append(aliasParts, aPrefix+alias)
|
||||
}
|
||||
|
||||
names := fmt.Sprintf("%s[%s]%s", prefix, bif.inversePrefix(), bif.Name)
|
||||
if len(aliasParts) > 0 {
|
||||
names = names + ", " + strings.Join(aliasParts, ", ")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%s", names, out[i:])
|
||||
}
|
||||
|
||||
// IsBoolFlag returns whether the flag doesnt need to accept args
|
||||
func (bif *BoolWithInverseFlag) IsBoolFlag() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Count returns the number of times this flag has been invoked
|
||||
func (bif *BoolWithInverseFlag) Count() int {
|
||||
return bif.count
|
||||
}
|
||||
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (bif *BoolWithInverseFlag) GetDefaultText() string {
|
||||
if bif.Required {
|
||||
return bif.DefaultText
|
||||
}
|
||||
return boolValue{}.ToString(bif.Value)
|
||||
}
|
||||
|
||||
// GetCategory returns the category of the flag
|
||||
func (bif *BoolWithInverseFlag) GetCategory() string {
|
||||
return bif.Category
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) SetCategory(c string) {
|
||||
bif.Category = c
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (bif *BoolWithInverseFlag) GetUsage() string {
|
||||
return bif.Usage
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (bif *BoolWithInverseFlag) GetEnvVars() []string {
|
||||
return bif.Sources.EnvKeys()
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (bif *BoolWithInverseFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) TakesValue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsDefaultVisible returns true if the flag is not hidden, otherwise false
|
||||
func (bif *BoolWithInverseFlag) IsDefaultVisible() bool {
|
||||
return !bif.HideDefault
|
||||
}
|
||||
|
||||
// TypeName is used for stringify/docs. For bool its a no-op
|
||||
func (bif *BoolWithInverseFlag) TypeName() string {
|
||||
return "bool"
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DurationFlag = FlagBase[time.Duration, NoConfig, durationValue]
|
||||
|
||||
// -- time.Duration Value
|
||||
type durationValue time.Duration
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (d durationValue) Create(val time.Duration, p *time.Duration, c NoConfig) Value {
|
||||
*p = val
|
||||
return (*durationValue)(p)
|
||||
}
|
||||
|
||||
func (d durationValue) ToString(val time.Duration) string {
|
||||
d = durationValue(val)
|
||||
return d.String()
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (d *durationValue) Set(s string) error {
|
||||
v, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*d = durationValue(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *durationValue) Get() any { return time.Duration(*d) }
|
||||
|
||||
func (d *durationValue) String() string {
|
||||
return fmt.Sprintf("%v", time.Duration(*d))
|
||||
}
|
||||
|
||||
func (cmd *Command) Duration(name string) time.Duration {
|
||||
if v, ok := cmd.Value(name).(time.Duration); ok {
|
||||
tracef("duration available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("duration NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return 0
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package cli
|
||||
|
||||
import "flag"
|
||||
|
||||
type extFlag struct {
|
||||
f *flag.Flag
|
||||
}
|
||||
|
||||
func (e *extFlag) PreParse() error {
|
||||
if e.f.DefValue != "" {
|
||||
return e.Set("", e.f.DefValue)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *extFlag) PostParse() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *extFlag) Set(_ string, val string) error {
|
||||
return e.f.Value.Set(val)
|
||||
}
|
||||
|
||||
func (e *extFlag) Get() any {
|
||||
return e.f.Value.(flag.Getter).Get()
|
||||
}
|
||||
|
||||
func (e *extFlag) Names() []string {
|
||||
return []string{e.f.Name}
|
||||
}
|
||||
|
||||
func (e *extFlag) IsSet() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *extFlag) String() string {
|
||||
return FlagStringer(e)
|
||||
}
|
||||
|
||||
func (e *extFlag) IsVisible() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *extFlag) TakesValue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *extFlag) GetUsage() string {
|
||||
return e.f.Usage
|
||||
}
|
||||
|
||||
func (e *extFlag) GetValue() string {
|
||||
return e.f.Value.String()
|
||||
}
|
||||
|
||||
func (e *extFlag) GetDefaultText() string {
|
||||
return e.f.DefValue
|
||||
}
|
||||
|
||||
func (e *extFlag) GetEnvVars() []string {
|
||||
return nil
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type (
|
||||
FloatFlag = FlagBase[float64, NoConfig, floatValue[float64]]
|
||||
Float32Flag = FlagBase[float32, NoConfig, floatValue[float32]]
|
||||
Float64Flag = FlagBase[float64, NoConfig, floatValue[float64]]
|
||||
)
|
||||
|
||||
// -- float Value
|
||||
type floatValue[T float32 | float64] struct {
|
||||
val *T
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (f floatValue[T]) Create(val T, p *T, c NoConfig) Value {
|
||||
*p = val
|
||||
|
||||
return &floatValue[T]{val: p}
|
||||
}
|
||||
|
||||
func (f floatValue[T]) ToString(b T) string {
|
||||
f.val = &b
|
||||
return f.String()
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (f *floatValue[T]) Set(s string) error {
|
||||
v, err := strconv.ParseFloat(s, int(unsafe.Sizeof(T(0))*8))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*f.val = T(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *floatValue[T]) Get() any { return *f.val }
|
||||
|
||||
func (f *floatValue[T]) String() string {
|
||||
return strconv.FormatFloat(float64(*f.val), 'g', -1, int(unsafe.Sizeof(T(0))*8))
|
||||
}
|
||||
|
||||
// Float looks up the value of a local FloatFlag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Float(name string) float64 {
|
||||
return getFloat[float64](cmd, name)
|
||||
}
|
||||
|
||||
// Float32 looks up the value of a local Float32Flag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Float32(name string) float32 {
|
||||
return getFloat[float32](cmd, name)
|
||||
}
|
||||
|
||||
// Float64 looks up the value of a local Float64Flag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Float64(name string) float64 {
|
||||
return getFloat[float64](cmd, name)
|
||||
}
|
||||
|
||||
func getFloat[T float32 | float64](cmd *Command, name string) T {
|
||||
if v, ok := cmd.Value(name).(T); ok {
|
||||
tracef("float available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("float NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return 0
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package cli
|
||||
|
||||
type (
|
||||
FloatSlice = SliceBase[float64, NoConfig, floatValue[float64]]
|
||||
Float32Slice = SliceBase[float32, NoConfig, floatValue[float32]]
|
||||
Float64Slice = SliceBase[float64, NoConfig, floatValue[float64]]
|
||||
FloatSliceFlag = FlagBase[[]float64, NoConfig, FloatSlice]
|
||||
Float32SliceFlag = FlagBase[[]float32, NoConfig, Float32Slice]
|
||||
Float64SliceFlag = FlagBase[[]float64, NoConfig, Float64Slice]
|
||||
)
|
||||
|
||||
var (
|
||||
NewFloatSlice = NewSliceBase[float64, NoConfig, floatValue[float64]]
|
||||
NewFloat32Slice = NewSliceBase[float32, NoConfig, floatValue[float32]]
|
||||
NewFloat64Slice = NewSliceBase[float64, NoConfig, floatValue[float64]]
|
||||
)
|
||||
|
||||
// FloatSlice looks up the value of a local FloatSliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) FloatSlice(name string) []float64 {
|
||||
return getNumberSlice[float64](cmd, name)
|
||||
}
|
||||
|
||||
// Float32Slice looks up the value of a local Float32Slice, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) Float32Slice(name string) []float32 {
|
||||
return getNumberSlice[float32](cmd, name)
|
||||
}
|
||||
|
||||
// Float64Slice looks up the value of a local Float64SliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) Float64Slice(name string) []float64 {
|
||||
return getNumberSlice[float64](cmd, name)
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package cli
|
||||
|
||||
type GenericFlag = FlagBase[Value, NoConfig, genericValue]
|
||||
|
||||
// -- Value Value
|
||||
type genericValue struct {
|
||||
val Value
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (f genericValue) Create(val Value, p *Value, c NoConfig) Value {
|
||||
*p = val
|
||||
return &genericValue{
|
||||
val: *p,
|
||||
}
|
||||
}
|
||||
|
||||
func (f genericValue) ToString(b Value) string {
|
||||
f.val = b
|
||||
return f.String()
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (f *genericValue) Set(s string) error {
|
||||
if f.val != nil {
|
||||
return f.val.Set(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *genericValue) Get() any {
|
||||
if f.val != nil {
|
||||
return f.val.Get()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *genericValue) String() string {
|
||||
if f.val != nil {
|
||||
return f.val.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *genericValue) IsBoolFlag() bool {
|
||||
if f.val == nil {
|
||||
return false
|
||||
}
|
||||
bf, ok := f.val.(boolFlag)
|
||||
return ok && bf.IsBoolFlag()
|
||||
}
|
||||
|
||||
// Generic looks up the value of a local GenericFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) Generic(name string) Value {
|
||||
if v, ok := cmd.Value(name).(Value); ok {
|
||||
tracef("generic available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("generic NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user