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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@@ -11,13 +12,13 @@ import (
|
|||||||
"github.com/moby/sys/userns"
|
"github.com/moby/sys/userns"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer"
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
)
|
)
|
||||||
|
|
||||||
var checkpointCommand = cli.Command{
|
var checkpointCommand = &cli.Command{
|
||||||
Name: "checkpoint",
|
Name: "checkpoint",
|
||||||
Usage: "checkpoint a running container",
|
Usage: "checkpoint a running container",
|
||||||
ArgsUsage: `<container-id>
|
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
|
Where "<container-id>" is the name for the instance of the container to be
|
||||||
checkpointed.`,
|
checkpointed.`,
|
||||||
Description: `The checkpoint command saves the state of the container instance.`,
|
Description: `The checkpoint command saves the state of the container instance.`,
|
||||||
|
// Disable comma as separator for slice flags.
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{Name: "image-path", Value: "", Usage: "path for saving criu image files"},
|
&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: "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.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: "leave-running", Usage: "leave the process running after checkpointing"},
|
||||||
cli.BoolFlag{Name: "tcp-established", Usage: "allow open tcp connections"},
|
&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: "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: "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: "ext-unix-sk", Usage: "allow external unix sockets"},
|
||||||
cli.BoolFlag{Name: "shell-job", Usage: "allow shell jobs"},
|
&cli.BoolFlag{Name: "shell-job", Usage: "allow shell jobs"},
|
||||||
cli.BoolFlag{Name: "lazy-pages", Usage: "use userfaultfd to lazily restore memory pages"},
|
&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.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.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: "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.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.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.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.BoolFlag{Name: "auto-dedup", Usage: "enable auto deduplication of memory images"},
|
||||||
},
|
},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// XXX: Currently this is untested with rootless containers.
|
// XXX: Currently this is untested with rootless containers.
|
||||||
@@ -53,7 +56,7 @@ checkpointed.`,
|
|||||||
logrus.Warn("runc checkpoint is untested with rootless containers")
|
logrus.Warn("runc checkpoint is untested with rootless containers")
|
||||||
}
|
}
|
||||||
|
|
||||||
container, err := getContainer(context)
|
container, err := getContainer(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -64,7 +67,7 @@ checkpointed.`,
|
|||||||
if status == libcontainer.Created || status == libcontainer.Stopped {
|
if status == libcontainer.Created || status == libcontainer.Stopped {
|
||||||
return fmt.Errorf("Container cannot be checkpointed in %s state", status.String())
|
return fmt.Errorf("Container cannot be checkpointed in %s state", status.String())
|
||||||
}
|
}
|
||||||
options, err := criuOptions(context)
|
options, err := criuOptions(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -80,8 +83,8 @@ checkpointed.`,
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareImagePaths(context *cli.Context) (string, string, error) {
|
func prepareImagePaths(cmd *cli.Command) (string, string, error) {
|
||||||
imagePath := context.String("image-path")
|
imagePath := cmd.String("image-path")
|
||||||
if imagePath == "" {
|
if imagePath == "" {
|
||||||
imagePath = getDefaultImagePath()
|
imagePath = getDefaultImagePath()
|
||||||
}
|
}
|
||||||
@@ -90,7 +93,7 @@ func prepareImagePaths(context *cli.Context) (string, string, error) {
|
|||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
parentPath := context.String("parent-path")
|
parentPath := cmd.String("parent-path")
|
||||||
if parentPath == "" {
|
if parentPath == "" {
|
||||||
return imagePath, parentPath, nil
|
return imagePath, parentPath, nil
|
||||||
}
|
}
|
||||||
@@ -112,35 +115,35 @@ func prepareImagePaths(context *cli.Context) (string, string, error) {
|
|||||||
return imagePath, parentPath, nil
|
return imagePath, parentPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func criuOptions(context *cli.Context) (*libcontainer.CriuOpts, error) {
|
func criuOptions(cmd *cli.Command) (*libcontainer.CriuOpts, error) {
|
||||||
imagePath, parentPath, err := prepareImagePaths(context)
|
imagePath, parentPath, err := prepareImagePaths(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := &libcontainer.CriuOpts{
|
opts := &libcontainer.CriuOpts{
|
||||||
ImagesDirectory: imagePath,
|
ImagesDirectory: imagePath,
|
||||||
WorkDirectory: context.String("work-path"),
|
WorkDirectory: cmd.String("work-path"),
|
||||||
ParentImage: parentPath,
|
ParentImage: parentPath,
|
||||||
LeaveRunning: context.Bool("leave-running"),
|
LeaveRunning: cmd.Bool("leave-running"),
|
||||||
TcpEstablished: context.Bool("tcp-established"),
|
TcpEstablished: cmd.Bool("tcp-established"),
|
||||||
TcpSkipInFlight: context.Bool("tcp-skip-in-flight"),
|
TcpSkipInFlight: cmd.Bool("tcp-skip-in-flight"),
|
||||||
LinkRemap: context.Bool("link-remap"),
|
LinkRemap: cmd.Bool("link-remap"),
|
||||||
ExternalUnixConnections: context.Bool("ext-unix-sk"),
|
ExternalUnixConnections: cmd.Bool("ext-unix-sk"),
|
||||||
ShellJob: context.Bool("shell-job"),
|
ShellJob: cmd.Bool("shell-job"),
|
||||||
FileLocks: context.Bool("file-locks"),
|
FileLocks: cmd.Bool("file-locks"),
|
||||||
PreDump: context.Bool("pre-dump"),
|
PreDump: cmd.Bool("pre-dump"),
|
||||||
AutoDedup: context.Bool("auto-dedup"),
|
AutoDedup: cmd.Bool("auto-dedup"),
|
||||||
LazyPages: context.Bool("lazy-pages"),
|
LazyPages: cmd.Bool("lazy-pages"),
|
||||||
StatusFd: context.Int("status-fd"),
|
StatusFd: cmd.Int("status-fd"),
|
||||||
LsmProfile: context.String("lsm-profile"),
|
LsmProfile: cmd.String("lsm-profile"),
|
||||||
LsmMountContext: context.String("lsm-mount-context"),
|
LsmMountContext: cmd.String("lsm-mount-context"),
|
||||||
ManageCgroupsMode: context.String("manage-cgroups-mode"),
|
ManageCgroupsMode: cmd.String("manage-cgroups-mode"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// CRIU options below may or may not be set.
|
// 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)
|
address, port, err := net.SplitHostPort(psOpt)
|
||||||
|
|
||||||
if err != nil || address == "" || port == "" {
|
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.
|
// runc doesn't manage network devices and their configuration.
|
||||||
nsmask := unix.CLONE_NEWNET
|
nsmask := unix.CLONE_NEWNET
|
||||||
|
|
||||||
if context.IsSet("empty-ns") {
|
if cmd.IsSet("empty-ns") {
|
||||||
namespaceMapping := map[specs.LinuxNamespaceType]int{
|
namespaceMapping := map[specs.LinuxNamespaceType]int{
|
||||||
specs.NetworkNamespace: unix.CLONE_NEWNET,
|
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)]
|
f, exists := namespaceMapping[specs.LinuxNamespaceType(ns)]
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, fmt.Errorf("namespace %q is not supported", ns)
|
return nil, fmt.Errorf("namespace %q is not supported", ns)
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var createCommand = cli.Command{
|
var createCommand = &cli.Command{
|
||||||
Name: "create",
|
Name: "create",
|
||||||
Usage: "create a container",
|
Usage: "create a container",
|
||||||
ArgsUsage: `<container-id>
|
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
|
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
|
command(s) that get executed on start, edit the args parameter of the spec. See
|
||||||
"runc spec --help" for more explanation.`,
|
"runc spec --help" for more explanation.`,
|
||||||
|
// Disable comma as separator for slice flags.
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "bundle, b",
|
Name: "bundle",
|
||||||
|
Aliases: []string{"b"},
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "console-socket",
|
Name: "console-socket",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
|
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",
|
Name: "pidfd-socket",
|
||||||
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the init process",
|
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the init process",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "pid-file",
|
Name: "pid-file",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "specify the file to write the process id to",
|
Usage: "specify the file to write the process id to",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "no-pivot",
|
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",
|
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",
|
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",
|
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",
|
Name: "preserve-fds",
|
||||||
Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)",
|
Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
status, err := startContainer(context, CT_ACT_CREATE, nil)
|
status, err := startContainer(cmd, CT_ACT_CREATE, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// exit with the container's exit status so any external supervisor
|
// exit with the container's exit status so any external supervisor
|
||||||
// is notified of the exit with the correct exit status.
|
// is notified of the exit with the correct exit status.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -8,7 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer"
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
@@ -24,7 +25,7 @@ func killContainer(container *libcontainer.Container) error {
|
|||||||
return errors.New("container init still running")
|
return errors.New("container init still running")
|
||||||
}
|
}
|
||||||
|
|
||||||
var deleteCommand = cli.Command{
|
var deleteCommand = &cli.Command{
|
||||||
Name: "delete",
|
Name: "delete",
|
||||||
Usage: "delete any resources held by the container often used with detached container",
|
Usage: "delete any resources held by the container often used with detached container",
|
||||||
ArgsUsage: `<container-id>
|
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:
|
"ubuntu01" removing "ubuntu01" from the runc list of containers:
|
||||||
|
|
||||||
# runc delete ubuntu01`,
|
# runc delete ubuntu01`,
|
||||||
|
// Disable comma as separator for slice flags.
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "force, f",
|
Name: "force",
|
||||||
|
Aliases: []string{"f"},
|
||||||
Usage: "Forcibly deletes the container if it is still running (uses SIGKILL)",
|
Usage: "Forcibly deletes the container if it is still running (uses SIGKILL)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
id := context.Args().First()
|
id := cmd.Args().First()
|
||||||
force := context.Bool("force")
|
force := cmd.Bool("force")
|
||||||
container, err := getContainer(context)
|
container, err := getContainer(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, libcontainer.ErrNotExist) {
|
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
|
// 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.
|
// 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 {
|
if e := os.RemoveAll(path); e != nil {
|
||||||
fmt.Fprintf(os.Stderr, "remove %s: %v\n", path, e)
|
fmt.Fprintf(os.Stderr, "remove %s: %v\n", path, e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -14,10 +15,10 @@ import (
|
|||||||
"github.com/opencontainers/runc/types"
|
"github.com/opencontainers/runc/types"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var eventsCommand = cli.Command{
|
var eventsCommand = &cli.Command{
|
||||||
Name: "events",
|
Name: "events",
|
||||||
Usage: "display container events such as OOM notifications, cpu, memory, and IO usage statistics",
|
Usage: "display container events such as OOM notifications, cpu, memory, and IO usage statistics",
|
||||||
ArgsUsage: `<container-id>
|
ArgsUsage: `<container-id>
|
||||||
@@ -25,19 +26,21 @@ var eventsCommand = cli.Command{
|
|||||||
Where "<container-id>" is the name for the instance of the container.`,
|
Where "<container-id>" is the name for the instance of the container.`,
|
||||||
Description: `The events command displays information about the container. By default the
|
Description: `The events command displays information about the container. By default the
|
||||||
information is displayed once every 5 seconds.`,
|
information is displayed once every 5 seconds.`,
|
||||||
|
// Disable comma as separator for slice flags.
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.DurationFlag{Name: "interval", Value: 5 * time.Second, Usage: "set the stats collection interval"},
|
&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.BoolFlag{Name: "stats", Usage: "display the container's stats then exit"},
|
||||||
},
|
},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
container, err := getContainer(context)
|
container, err := getContainer(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
duration := context.Duration("interval")
|
duration := cmd.Duration("interval")
|
||||||
if duration <= 0 {
|
if duration <= 0 {
|
||||||
return errors.New("duration interval must be greater than 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()
|
s, err := container.Stats()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -72,7 +75,7 @@ information is displayed once every 5 seconds.`,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
for range time.Tick(context.Duration("interval")) {
|
for range time.Tick(cmd.Duration("interval")) {
|
||||||
s, err := container.Stats()
|
s, err := container.Stats()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error(err)
|
logrus.Error(err)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -11,10 +12,10 @@ import (
|
|||||||
"github.com/opencontainers/runc/libcontainer"
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
"github.com/opencontainers/runc/libcontainer/utils"
|
"github.com/opencontainers/runc/libcontainer/utils"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"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",
|
Name: "exec",
|
||||||
Usage: "execute new process inside the container",
|
Usage: "execute new process inside the container",
|
||||||
ArgsUsage: `<container-id> <command> [command options] || -p process.json <container-id>
|
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:
|
following will output a list of processes running in the container:
|
||||||
|
|
||||||
# runc exec <container-id> ps`,
|
# 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{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "console-socket",
|
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",
|
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",
|
Name: "pidfd-socket",
|
||||||
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the exec process",
|
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the exec process",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "cwd",
|
Name: "cwd",
|
||||||
Usage: "current working directory in the container",
|
Usage: "current working directory in the container",
|
||||||
},
|
},
|
||||||
cli.StringSliceFlag{
|
&cli.StringSliceFlag{
|
||||||
Name: "env, e",
|
Name: "env",
|
||||||
|
Aliases: []string{"e"},
|
||||||
Usage: "set environment variables",
|
Usage: "set environment variables",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "tty, t",
|
Name: "tty",
|
||||||
|
Aliases: []string{"t"},
|
||||||
Usage: "allocate a pseudo-TTY",
|
Usage: "allocate a pseudo-TTY",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "user, u",
|
Name: "user",
|
||||||
|
Aliases: []string{"u"},
|
||||||
Usage: "UID (format: <uid>[:<gid>])",
|
Usage: "UID (format: <uid>[:<gid>])",
|
||||||
},
|
},
|
||||||
cli.Int64SliceFlag{
|
&cli.Int64SliceFlag{
|
||||||
Name: "additional-gids, g",
|
Name: "additional-gids",
|
||||||
|
Aliases: []string{"g"},
|
||||||
Usage: "additional gids",
|
Usage: "additional gids",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "process, p",
|
Name: "process",
|
||||||
|
Aliases: []string{"p"},
|
||||||
Usage: "path to the process.json",
|
Usage: "path to the process.json",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "detach,d",
|
Name: "detach",
|
||||||
|
Aliases: []string{"d"},
|
||||||
Usage: "detach from the container's process",
|
Usage: "detach from the container's process",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "pid-file",
|
Name: "pid-file",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "specify the file to write the process id to",
|
Usage: "specify the file to write the process id to",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "process-label",
|
Name: "process-label",
|
||||||
Usage: "set the asm process label for the process commonly used with selinux",
|
Usage: "set the asm process label for the process commonly used with selinux",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "apparmor",
|
Name: "apparmor",
|
||||||
Usage: "set the apparmor profile for the process",
|
Usage: "set the apparmor profile for the process",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "no-new-privs",
|
Name: "no-new-privs",
|
||||||
Usage: "set the no new privileges value for the process",
|
Usage: "set the no new privileges value for the process",
|
||||||
},
|
},
|
||||||
cli.StringSliceFlag{
|
&cli.StringSliceFlag{
|
||||||
Name: "cap, c",
|
Name: "cap",
|
||||||
Value: &cli.StringSlice{},
|
Aliases: []string{"c"},
|
||||||
|
Value: []string{},
|
||||||
Usage: "add a capability to the bounding set for the process",
|
Usage: "add a capability to the bounding set for the process",
|
||||||
},
|
},
|
||||||
cli.IntFlag{
|
&cli.IntFlag{
|
||||||
Name: "preserve-fds",
|
Name: "preserve-fds",
|
||||||
Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)",
|
Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)",
|
||||||
},
|
},
|
||||||
cli.StringSliceFlag{
|
&cli.StringSliceFlag{
|
||||||
Name: "cgroup",
|
Name: "cgroup",
|
||||||
Usage: "run the process in an (existing) sub-cgroup(s). Format is [<controller>:]<cgroup>.",
|
Usage: "run the process in an (existing) sub-cgroup(s). Format is [<controller>:]<cgroup>.",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "ignore-paused",
|
Name: "ignore-paused",
|
||||||
Usage: "allow exec in a paused container",
|
Usage: "allow exec in a paused container",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||||
if err := checkArgs(context, 1, minArgs); err != nil {
|
if err := checkArgs(cmd, 1, minArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := revisePidFile(context); err != nil {
|
if err := revisePidFile(cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
status, err := execProcess(context)
|
status, err := execProcess(cmd)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
os.Exit(status)
|
os.Exit(status)
|
||||||
}
|
}
|
||||||
fatalWithCode(fmt.Errorf("exec failed: %w", err), 255)
|
fatalWithCode(fmt.Errorf("exec failed: %w", err), 255)
|
||||||
return nil // to satisfy the linter
|
return nil // to satisfy the linter
|
||||||
},
|
},
|
||||||
SkipArgReorder: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getSubCgroupPaths parses --cgroup arguments, which can either be
|
// getSubCgroupPaths parses --cgroup arguments, which can either be
|
||||||
@@ -152,8 +165,8 @@ func getSubCgroupPaths(args []string) (map[string]string, error) {
|
|||||||
return paths, nil
|
return paths, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func execProcess(context *cli.Context) (int, error) {
|
func execProcess(cmd *cli.Command) (int, error) {
|
||||||
container, err := getContainer(context)
|
container, err := getContainer(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
@@ -164,15 +177,15 @@ func execProcess(context *cli.Context) (int, error) {
|
|||||||
if status == libcontainer.Stopped {
|
if status == libcontainer.Stopped {
|
||||||
return -1, errors.New("cannot exec in a stopped container")
|
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)")
|
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 {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cgPaths, err := getSubCgroupPaths(context.StringSlice("cgroup"))
|
cgPaths, err := getSubCgroupPaths(cmd.StringSlice("cgroup"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
@@ -181,20 +194,20 @@ func execProcess(context *cli.Context) (int, error) {
|
|||||||
enableSubreaper: false,
|
enableSubreaper: false,
|
||||||
shouldDestroy: false,
|
shouldDestroy: false,
|
||||||
container: container,
|
container: container,
|
||||||
consoleSocket: context.String("console-socket"),
|
consoleSocket: cmd.String("console-socket"),
|
||||||
pidfdSocket: context.String("pidfd-socket"),
|
pidfdSocket: cmd.String("pidfd-socket"),
|
||||||
detach: context.Bool("detach"),
|
detach: cmd.Bool("detach"),
|
||||||
pidFile: context.String("pid-file"),
|
pidFile: cmd.String("pid-file"),
|
||||||
action: CT_ACT_RUN,
|
action: CT_ACT_RUN,
|
||||||
init: false,
|
init: false,
|
||||||
preserveFDs: context.Int("preserve-fds"),
|
preserveFDs: cmd.Int("preserve-fds"),
|
||||||
subCgroupPaths: cgPaths,
|
subCgroupPaths: cgPaths,
|
||||||
}
|
}
|
||||||
return r.run(p)
|
return r.run(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getProcess(context *cli.Context, c *libcontainer.Container) (*specs.Process, error) {
|
func getProcess(cmd *cli.Command, c *libcontainer.Container) (*specs.Process, error) {
|
||||||
if path := context.String("process"); path != "" {
|
if path := cmd.String("process"); path != "" {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -219,22 +232,22 @@ func getProcess(context *cli.Context, c *libcontainer.Container) (*specs.Process
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
p := spec.Process
|
p := spec.Process
|
||||||
args := context.Args()
|
args := cmd.Args().Slice()
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return nil, errors.New("exec args cannot be empty")
|
return nil, errors.New("exec args cannot be empty")
|
||||||
}
|
}
|
||||||
p.Args = args[1:]
|
p.Args = args[1:]
|
||||||
// Override the cwd, if passed.
|
// Override the cwd, if passed.
|
||||||
if cwd := context.String("cwd"); cwd != "" {
|
if cwd := cmd.String("cwd"); cwd != "" {
|
||||||
p.Cwd = cwd
|
p.Cwd = cwd
|
||||||
}
|
}
|
||||||
if ap := context.String("apparmor"); ap != "" {
|
if ap := cmd.String("apparmor"); ap != "" {
|
||||||
p.ApparmorProfile = ap
|
p.ApparmorProfile = ap
|
||||||
}
|
}
|
||||||
if l := context.String("process-label"); l != "" {
|
if l := cmd.String("process-label"); l != "" {
|
||||||
p.SelinuxLabel = l
|
p.SelinuxLabel = l
|
||||||
}
|
}
|
||||||
if caps := context.StringSlice("cap"); len(caps) > 0 {
|
if caps := cmd.StringSlice("cap"); len(caps) > 0 {
|
||||||
for _, c := range caps {
|
for _, c := range caps {
|
||||||
p.Capabilities.Bounding = append(p.Capabilities.Bounding, c)
|
p.Capabilities.Bounding = append(p.Capabilities.Bounding, c)
|
||||||
p.Capabilities.Effective = append(p.Capabilities.Effective, 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
|
// 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.
|
// Always set tty to false, unless explicitly enabled from CLI.
|
||||||
p.Terminal = context.Bool("tty")
|
p.Terminal = cmd.Bool("tty")
|
||||||
if context.IsSet("no-new-privs") {
|
if cmd.IsSet("no-new-privs") {
|
||||||
p.NoNewPrivileges = context.Bool("no-new-privs")
|
p.NoNewPrivileges = cmd.Bool("no-new-privs")
|
||||||
}
|
}
|
||||||
// Override the user, if passed.
|
// Override the user, if passed.
|
||||||
if user := context.String("user"); user != "" {
|
if user := cmd.String("user"); user != "" {
|
||||||
uids, gids, ok := strings.Cut(user, ":")
|
uids, gids, ok := strings.Cut(user, ":")
|
||||||
if ok {
|
if ok {
|
||||||
gid, err := strconv.Atoi(gids)
|
gid, err := strconv.Atoi(gids)
|
||||||
@@ -271,7 +284,7 @@ func getProcess(context *cli.Context, c *libcontainer.Container) (*specs.Process
|
|||||||
}
|
}
|
||||||
p.User.UID = uint32(uid)
|
p.User.UID = uint32(uid)
|
||||||
}
|
}
|
||||||
for _, gid := range context.Int64Slice("additional-gids") {
|
for _, gid := range cmd.Int64Slice("additional-gids") {
|
||||||
if gid < 0 {
|
if gid < 0 {
|
||||||
return nil, fmt.Errorf("additional-gids must be a positive number %d", gid)
|
return nil, fmt.Errorf("additional-gids must be a positive number %d", gid)
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-5
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
@@ -11,10 +12,10 @@ import (
|
|||||||
runcfeatures "github.com/opencontainers/runc/types/features"
|
runcfeatures "github.com/opencontainers/runc/types/features"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go/features"
|
"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",
|
Name: "features",
|
||||||
Usage: "show the enabled features",
|
Usage: "show the enabled features",
|
||||||
ArgsUsage: "",
|
ArgsUsage: "",
|
||||||
@@ -22,8 +23,10 @@ var featuresCommand = cli.Command{
|
|||||||
The result is parsable as a JSON.
|
The result is parsable as a JSON.
|
||||||
See https://github.com/opencontainers/runtime-spec/blob/main/features.md for the type definition.
|
See https://github.com/opencontainers/runtime-spec/blob/main/features.md for the type definition.
|
||||||
`,
|
`,
|
||||||
Action: func(context *cli.Context) error {
|
// Disable comma as separator for slice flags.
|
||||||
if err := checkArgs(context, 0, exactArgs); err != nil {
|
DisableSliceFlagSeparator: true,
|
||||||
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||||
|
if err := checkArgs(cmd, 0, exactArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +96,7 @@ var featuresCommand = cli.Command{
|
|||||||
feat.Annotations[runcfeatures.AnnotationLibseccompVersion] = fmt.Sprintf("%d.%d.%d", major, minor, patch)
|
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("", " ")
|
enc.SetIndent("", " ")
|
||||||
return enc.Encode(feat)
|
return enc.Encode(feat)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ require (
|
|||||||
github.com/opencontainers/selinux v1.14.1
|
github.com/opencontainers/selinux v1.14.1
|
||||||
github.com/seccomp/libseccomp-golang v0.11.1
|
github.com/seccomp/libseccomp-golang v0.11.1
|
||||||
github.com/sirupsen/logrus v1.9.4
|
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/netlink v1.3.1
|
||||||
github.com/vishvananda/netns v0.0.5
|
github.com/vishvananda/netns v0.0.5
|
||||||
golang.org/x/net v0.54.0
|
golang.org/x/net v0.54.0
|
||||||
@@ -31,6 +31,4 @@ require (
|
|||||||
require (
|
require (
|
||||||
cyphar.com/go-pathrs v0.2.4 // indirect
|
cyphar.com/go-pathrs v0.2.4 // indirect
|
||||||
github.com/cilium/ebpf v0.17.3 // 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 h1:iD/mge36swa1UFKdINkr1Frkpp6wZsy3YYEildj9cLY=
|
||||||
cyphar.com/go-pathrs v0.2.4/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
|
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 h1:qGiWA4App1gGlEfIJ68WR9jbezV9J7yZdjzglezcqKo=
|
||||||
github.com/checkpoint-restore/go-criu/v7 v7.2.0/go.mod h1:u0LCWLg0w4yqqu14aXhiB4YD3a1qd8EcCEg7vda5dwo=
|
github.com/checkpoint-restore/go-criu/v7 v7.2.0/go.mod h1:u0LCWLg0w4yqqu14aXhiB4YD3a1qd8EcCEg7vda5dwo=
|
||||||
github.com/cilium/ebpf v0.17.3 h1:FnP4r16PWYSE4ux6zN+//jMcW4nMVRvuTLVTvCjyyjg=
|
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/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 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
|
||||||
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
|
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 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||||
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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=
|
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/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 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
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 h1:wuk4ZjSx6kyQII4rj6G6fvVzRHQaSiPvccJazDagu4g=
|
||||||
github.com/seccomp/libseccomp-golang v0.11.1/go.mod h1:5m1Lk8E9OwgZTTVz4bBOer7JuazaBa+xTkM895tDiWc=
|
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 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
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 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
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/v3 v3.9.0 h1:AV9lIiPv3ukYnxunaCUsHnEozptYmDN2F0+yWqLMn/c=
|
||||||
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
|
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 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||||
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
||||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
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=
|
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 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer"
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
var killCommand = cli.Command{
|
var killCommand = &cli.Command{
|
||||||
Name: "kill",
|
Name: "kill",
|
||||||
Usage: "kill sends the specified signal (default: SIGTERM) to the container's init process",
|
Usage: "kill sends the specified signal (default: SIGTERM) to the container's init process",
|
||||||
ArgsUsage: `<container-id> [signal]
|
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:
|
signal to the init process of the "ubuntu01" container:
|
||||||
|
|
||||||
# runc kill ubuntu01 KILL`,
|
# 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{
|
Flags: []cli.Flag{
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "all, a",
|
Name: "all",
|
||||||
|
Aliases: []string{"a"},
|
||||||
Usage: "(obsoleted, do not use)",
|
Usage: "(obsoleted, do not use)",
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||||
if err := checkArgs(context, 1, minArgs); err != nil {
|
if err := checkArgs(cmd, 1, minArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := checkArgs(context, 2, maxArgs); err != nil {
|
if err := checkArgs(cmd, 2, maxArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
container, err := getContainer(context)
|
container, err := getContainer(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sigstr := context.Args().Get(1)
|
sigstr := cmd.Args().Get(1)
|
||||||
if sigstr == "" {
|
if sigstr == "" {
|
||||||
sigstr = "SIGTERM"
|
sigstr = "SIGTERM"
|
||||||
}
|
}
|
||||||
@@ -53,7 +59,7 @@ signal to the init process of the "ubuntu01" container:
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = container.Signal(signal)
|
err = container.Signal(signal)
|
||||||
if errors.Is(err, libcontainer.ErrNotRunning) && context.Bool("all") {
|
if errors.Is(err, libcontainer.ErrNotRunning) && cmd.Bool("all") {
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -13,7 +14,7 @@ import (
|
|||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer"
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
"github.com/opencontainers/runc/libcontainer/utils"
|
"github.com/opencontainers/runc/libcontainer/utils"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const formatOptions = `table or json`
|
const formatOptions = `table or json`
|
||||||
@@ -41,7 +42,7 @@ type containerState struct {
|
|||||||
Owner string `json:"owner"`
|
Owner string `json:"owner"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var listCommand = cli.Command{
|
var listCommand = &cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Usage: "lists containers started by runc with the given root",
|
Usage: "lists containers started by runc with the given root",
|
||||||
ArgsUsage: `
|
ArgsUsage: `
|
||||||
@@ -56,34 +57,38 @@ To list containers created via the default "--root":
|
|||||||
EXAMPLE 2:
|
EXAMPLE 2:
|
||||||
To list containers created using a non-default value for "--root":
|
To list containers created using a non-default value for "--root":
|
||||||
# runc --root value list`,
|
# runc --root value list`,
|
||||||
|
// Disable comma as separator for slice flags.
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "format, f",
|
Name: "format",
|
||||||
|
Aliases: []string{"f"},
|
||||||
Value: "table",
|
Value: "table",
|
||||||
Usage: `select one of: ` + formatOptions,
|
Usage: `select one of: ` + formatOptions,
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "quiet, q",
|
Name: "quiet",
|
||||||
|
Aliases: []string{"q"},
|
||||||
Usage: "display only container IDs",
|
Usage: "display only container IDs",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||||
if err := checkArgs(context, 0, exactArgs); err != nil {
|
if err := checkArgs(cmd, 0, exactArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s, err := getContainers(context)
|
s, err := getContainers(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.Bool("quiet") {
|
if cmd.Bool("quiet") {
|
||||||
for _, item := range s {
|
for _, item := range s {
|
||||||
fmt.Println(item.ID)
|
fmt.Println(item.ID)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch context.String("format") {
|
switch cmd.String("format") {
|
||||||
case "table":
|
case "table":
|
||||||
w := tabwriter.NewWriter(os.Stdout, 12, 1, 3, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 12, 1, 3, ' ', 0)
|
||||||
fmt.Fprint(w, "ID\tPID\tSTATUS\tBUNDLE\tCREATED\tOWNER\n")
|
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) {
|
func getContainers(cmd *cli.Command) ([]containerState, error) {
|
||||||
root := context.GlobalString("root")
|
root := cmd.String("root")
|
||||||
list, err := os.ReadDir(root)
|
list, err := os.ReadDir(root)
|
||||||
if err != nil {
|
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
|
// Ignore non-existing default root directory
|
||||||
// (no containers created yet).
|
// (no containers created yet).
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -20,7 +21,7 @@ import (
|
|||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// version is set from the contents of VERSION file.
|
// version is set from the contents of VERSION file.
|
||||||
@@ -37,10 +38,10 @@ var extraVersion = ""
|
|||||||
// and will be populated by the Makefile.
|
// and will be populated by the Makefile.
|
||||||
var gitCommit = ""
|
var gitCommit = ""
|
||||||
|
|
||||||
func printVersion(c *cli.Context) {
|
func printVersion(c *cli.Command) {
|
||||||
w := c.App.Writer
|
w := c.Writer
|
||||||
|
|
||||||
fmt.Fprintln(w, "runc version", c.App.Version)
|
fmt.Fprintln(w, "runc version", c.Version)
|
||||||
if gitCommit != "" {
|
if gitCommit != "" {
|
||||||
fmt.Fprintln(w, "commit:", gitCommit)
|
fmt.Fprintln(w, "commit:", gitCommit)
|
||||||
}
|
}
|
||||||
@@ -81,10 +82,12 @@ value for "bundle" is the current directory.`
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := cli.NewApp()
|
app := &cli.Command{}
|
||||||
app.Name = "runc"
|
app.Name = "runc"
|
||||||
app.Version = strings.TrimSpace(version) + extraVersion
|
app.Version = strings.TrimSpace(version) + extraVersion
|
||||||
app.Usage = usage
|
app.Usage = usage
|
||||||
|
// Disable comma as separator for slice flags.
|
||||||
|
app.DisableSliceFlagSeparator = true
|
||||||
|
|
||||||
cli.VersionPrinter = printVersion
|
cli.VersionPrinter = printVersion
|
||||||
|
|
||||||
@@ -97,36 +100,36 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.Flags = []cli.Flag{
|
app.Flags = []cli.Flag{
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "debug",
|
Name: "debug",
|
||||||
Usage: "enable debug logging",
|
Usage: "enable debug logging",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "log",
|
Name: "log",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "set the log file to write runc logs to (default is '/dev/stderr')",
|
Usage: "set the log file to write runc logs to (default is '/dev/stderr')",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "log-format",
|
Name: "log-format",
|
||||||
Value: "text",
|
Value: "text",
|
||||||
Usage: "set the log format ('text' (default), or 'json')",
|
Usage: "set the log format ('text' (default), or 'json')",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "root",
|
Name: "root",
|
||||||
Value: root,
|
Value: root,
|
||||||
Usage: "root directory for storage of container state (this should be located in tmpfs)",
|
Usage: "root directory for storage of container state (this should be located in tmpfs)",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "systemd-cgroup",
|
Name: "systemd-cgroup",
|
||||||
Usage: "enable systemd cgroup support, expects cgroupsPath to be of form \"slice:prefix:name\" for e.g. \"system.slice:runc:434234\"",
|
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",
|
Name: "rootless",
|
||||||
Value: "auto",
|
Value: "auto",
|
||||||
Usage: "ignore cgroup permission errors ('true', 'false', or 'auto')",
|
Usage: "ignore cgroup permission errors ('true', 'false', or 'auto')",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []*cli.Command{
|
||||||
checkpointCommand,
|
checkpointCommand,
|
||||||
createCommand,
|
createCommand,
|
||||||
deleteCommand,
|
deleteCommand,
|
||||||
@@ -145,8 +148,8 @@ func main() {
|
|||||||
updateCommand,
|
updateCommand,
|
||||||
featuresCommand,
|
featuresCommand,
|
||||||
}
|
}
|
||||||
app.Before = func(context *cli.Context) error {
|
app.Before = func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
|
||||||
if !context.IsSet("root") && xdgDirUsed {
|
if !cmd.IsSet("root") && xdgDirUsed {
|
||||||
// According to the XDG specification, we need to set anything in
|
// 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
|
// XDG_RUNTIME_DIR to have a sticky bit if we don't want it to get
|
||||||
// auto-pruned.
|
// auto-pruned.
|
||||||
@@ -159,18 +162,22 @@ func main() {
|
|||||||
fatal(err)
|
fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := reviseRootDir(context); err != nil {
|
if err := reviseRootDir(cmd); err != nil {
|
||||||
return err
|
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
|
// If the command returns an error, cli takes upon itself to print
|
||||||
// the error on cli.ErrWriter and exit.
|
// the error on cli.ErrWriter and exit.
|
||||||
// Use our own writer here to ensure the log gets sent to the right location.
|
// Use our own writer here to ensure the log gets sent to the right location.
|
||||||
cli.ErrWriter = &FatalWriter{cli.ErrWriter}
|
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)
|
fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,8 +194,8 @@ func (f *FatalWriter) Write(p []byte) (n int, err error) {
|
|||||||
return len(p), nil
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func configLogrus(context *cli.Context) error {
|
func configLogrus(cmd *cli.Command) error {
|
||||||
if context.GlobalBool("debug") {
|
if cmd.Bool("debug") {
|
||||||
logrus.SetLevel(logrus.DebugLevel)
|
logrus.SetLevel(logrus.DebugLevel)
|
||||||
logrus.SetReportCaller(true)
|
logrus.SetReportCaller(true)
|
||||||
// Shorten function and file names reported by the logger, by
|
// 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 "":
|
case "":
|
||||||
// do nothing
|
// do nothing
|
||||||
case "text":
|
case "text":
|
||||||
@@ -216,7 +223,7 @@ func configLogrus(context *cli.Context) error {
|
|||||||
return errors.New("invalid log-format: " + f)
|
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)
|
f, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
+5
-5
@@ -15,7 +15,7 @@ import (
|
|||||||
"github.com/opencontainers/runc/libcontainer"
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,12 +25,12 @@ type notifySocket struct {
|
|||||||
socketPath string
|
socketPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNotifySocket(context *cli.Context, notifySocketHost, id string) *notifySocket {
|
func newNotifySocket(cmd *cli.Command, notifySocketHost, id string) *notifySocket {
|
||||||
if notifySocketHost == "" {
|
if notifySocketHost == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
root := filepath.Join(context.GlobalString("root"), id)
|
root := filepath.Join(cmd.String("root"), id)
|
||||||
socketPath := filepath.Join(root, "notify", "notify.sock")
|
socketPath := filepath.Join(root, "notify", "notify.sock")
|
||||||
|
|
||||||
notifySocket := ¬ifySocket{
|
notifySocket := ¬ifySocket{
|
||||||
@@ -84,8 +84,8 @@ func (s *notifySocket) setupSocketDirectory() error {
|
|||||||
return os.Mkdir(path.Dir(s.socketPath), 0o755)
|
return os.Mkdir(path.Dir(s.socketPath), 0o755)
|
||||||
}
|
}
|
||||||
|
|
||||||
func notifySocketStart(context *cli.Context, notifySocketHost, id string) (*notifySocket, error) {
|
func notifySocketStart(cmd *cli.Command, notifySocketHost, id string) (*notifySocket, error) {
|
||||||
notifySocket := newNotifySocket(context, notifySocketHost, id)
|
notifySocket := newNotifySocket(cmd, notifySocketHost, id)
|
||||||
if notifySocket == nil {
|
if notifySocket == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/urfave/cli"
|
"context"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var pauseCommand = cli.Command{
|
var pauseCommand = &cli.Command{
|
||||||
Name: "pause",
|
Name: "pause",
|
||||||
Usage: "pause suspends all processes inside the container",
|
Usage: "pause suspends all processes inside the container",
|
||||||
ArgsUsage: `<container-id>
|
ArgsUsage: `<container-id>
|
||||||
@@ -14,11 +16,13 @@ paused. `,
|
|||||||
Description: `The pause command suspends all processes in the instance of the container.
|
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.`,
|
Use runc list to identify instances of containers and their current status.`,
|
||||||
Action: func(context *cli.Context) error {
|
// Disable comma as separator for slice flags.
|
||||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
DisableSliceFlagSeparator: true,
|
||||||
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||||
|
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
container, err := getContainer(context)
|
container, err := getContainer(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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",
|
Name: "resume",
|
||||||
Usage: "resumes all processes that have been previously paused",
|
Usage: "resumes all processes that have been previously paused",
|
||||||
ArgsUsage: `<container-id>
|
ArgsUsage: `<container-id>
|
||||||
@@ -41,11 +45,13 @@ resumed.`,
|
|||||||
Description: `The resume command resumes all processes in the instance of the container.
|
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.`,
|
Use runc list to identify instances of containers and their current status.`,
|
||||||
Action: func(context *cli.Context) error {
|
// Disable comma as separator for slice flags.
|
||||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
DisableSliceFlagSeparator: true,
|
||||||
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||||
|
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
container, err := getContainer(context)
|
container, err := getContainer(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -10,26 +11,32 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var psCommand = cli.Command{
|
var psCommand = &cli.Command{
|
||||||
Name: "ps",
|
Name: "ps",
|
||||||
Usage: "ps displays the processes running inside a container",
|
Usage: "ps displays the processes running inside a container",
|
||||||
ArgsUsage: `<container-id> [ps options]`,
|
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{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "format, f",
|
Name: "format",
|
||||||
|
Aliases: []string{"f"},
|
||||||
Value: "table",
|
Value: "table",
|
||||||
Usage: `select one of: ` + formatOptions,
|
Usage: `select one of: ` + formatOptions,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||||
if err := checkArgs(context, 1, minArgs); err != nil {
|
if err := checkArgs(cmd, 1, minArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
container, err := getContainer(context)
|
container, err := getContainer(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -40,7 +47,7 @@ var psCommand = cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch context.String("format") {
|
switch cmd.String("format") {
|
||||||
case "table":
|
case "table":
|
||||||
case "json":
|
case "json":
|
||||||
return json.NewEncoder(os.Stdout).Encode(pids)
|
return json.NewEncoder(os.Stdout).Encode(pids)
|
||||||
@@ -49,16 +56,16 @@ var psCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// [1:] is to remove command name, ex:
|
// [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: [ps_arg1 ps_arg2 ...]
|
||||||
//
|
//
|
||||||
psArgs := context.Args()[1:]
|
psArgs := cmd.Args().Slice()[1:]
|
||||||
if len(psArgs) == 0 {
|
if len(psArgs) == 0 {
|
||||||
psArgs = []string{"-ef"}
|
psArgs = []string{"-ef"}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("ps", psArgs...)
|
cmdExec := exec.Command("ps", psArgs...)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmdExec.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %s", err, output)
|
return fmt.Errorf("%w: %s", err, output)
|
||||||
}
|
}
|
||||||
@@ -86,7 +93,6 @@ var psCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
SkipArgReorder: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPidIndex(title string) (int, error) {
|
func getPidIndex(title string) (int, error) {
|
||||||
|
|||||||
+31
-26
@@ -1,14 +1,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/moby/sys/userns"
|
"github.com/moby/sys/userns"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var restoreCommand = cli.Command{
|
var restoreCommand = &cli.Command{
|
||||||
Name: "restore",
|
Name: "restore",
|
||||||
Usage: "restore a container from a previous checkpoint",
|
Usage: "restore a container from a previous checkpoint",
|
||||||
ArgsUsage: `<container-id>
|
ArgsUsage: `<container-id>
|
||||||
@@ -17,90 +18,94 @@ Where "<container-id>" is the name for the instance of the container to be
|
|||||||
restored.`,
|
restored.`,
|
||||||
Description: `Restores the saved state of the container instance that was previously saved
|
Description: `Restores the saved state of the container instance that was previously saved
|
||||||
using the runc checkpoint command.`,
|
using the runc checkpoint command.`,
|
||||||
|
// Disable comma as separator for slice flags.
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "console-socket",
|
Name: "console-socket",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
|
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",
|
Name: "image-path",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "path to criu image files for restoring",
|
Usage: "path to criu image files for restoring",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "work-path",
|
Name: "work-path",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "path for saving work files and logs",
|
Usage: "path for saving work files and logs",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "tcp-established",
|
Name: "tcp-established",
|
||||||
Usage: "allow open tcp connections",
|
Usage: "allow open tcp connections",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "ext-unix-sk",
|
Name: "ext-unix-sk",
|
||||||
Usage: "allow external unix sockets",
|
Usage: "allow external unix sockets",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "shell-job",
|
Name: "shell-job",
|
||||||
Usage: "allow shell jobs",
|
Usage: "allow shell jobs",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "file-locks",
|
Name: "file-locks",
|
||||||
Usage: "handle file locks, for safety",
|
Usage: "handle file locks, for safety",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "manage-cgroups-mode",
|
Name: "manage-cgroups-mode",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "cgroups mode: soft|full|strict|ignore (default: soft)",
|
Usage: "cgroups mode: soft|full|strict|ignore (default: soft)",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "bundle, b",
|
Name: "bundle",
|
||||||
|
Aliases: []string{"b"},
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "path to the root of the bundle directory",
|
Usage: "path to the root of the bundle directory",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "detach,d",
|
Name: "detach",
|
||||||
|
Aliases: []string{"d"},
|
||||||
Usage: "detach from the container's process",
|
Usage: "detach from the container's process",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "pid-file",
|
Name: "pid-file",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "specify the file to write the process id to",
|
Usage: "specify the file to write the process id to",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "no-subreaper",
|
Name: "no-subreaper",
|
||||||
Usage: "disable the use of the subreaper used to reap reparented processes",
|
Usage: "disable the use of the subreaper used to reap reparented processes",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "no-pivot",
|
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",
|
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",
|
Name: "empty-ns",
|
||||||
Usage: "create a namespace, but don't restore its properties",
|
Usage: "create a namespace, but don't restore its properties",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "auto-dedup",
|
Name: "auto-dedup",
|
||||||
Usage: "enable auto deduplication of memory images",
|
Usage: "enable auto deduplication of memory images",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "lazy-pages",
|
Name: "lazy-pages",
|
||||||
Usage: "use userfaultfd to lazily restore memory pages",
|
Usage: "use userfaultfd to lazily restore memory pages",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "lsm-profile",
|
Name: "lsm-profile",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "Specify an LSM profile to be used during restore in the form of TYPE:NAME.",
|
Usage: "Specify an LSM profile to be used during restore in the form of TYPE:NAME.",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "lsm-mount-context",
|
Name: "lsm-mount-context",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "Specify an LSM mount context to be used during restore.",
|
Usage: "Specify an LSM mount context to be used during restore.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// XXX: Currently this is untested with rootless containers.
|
// 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")
|
logrus.Warn("runc checkpoint is untested with rootless containers")
|
||||||
}
|
}
|
||||||
|
|
||||||
options, err := criuOptions(context)
|
options, err := criuOptions(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
status, err := startContainer(context, CT_ACT_RESTORE, options)
|
status, err := startContainer(cmd, CT_ACT_RESTORE, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -5,14 +5,14 @@ import (
|
|||||||
|
|
||||||
"github.com/moby/sys/userns"
|
"github.com/moby/sys/userns"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
|
|
||||||
"github.com/opencontainers/cgroups/systemd"
|
"github.com/opencontainers/cgroups/systemd"
|
||||||
)
|
)
|
||||||
|
|
||||||
func shouldUseRootlessCgroupManager(context *cli.Context) (bool, error) {
|
func shouldUseRootlessCgroupManager(cmd *cli.Command) (bool, error) {
|
||||||
if context != nil {
|
if cmd != nil {
|
||||||
b, err := parseBoolOrAuto(context.GlobalString("rootless"))
|
b, err := parseBoolOrAuto(cmd.String("rootless"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
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,
|
// 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.
|
// mostly when $DBUS_SESSION_BUS_ADDRESS is unset.
|
||||||
if context.GlobalBool("systemd-cgroup") {
|
if cmd.Bool("systemd-cgroup") {
|
||||||
ownerUID, err := systemd.DetectUID()
|
ownerUID, err := systemd.DetectUID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Debug("failed to get the OwnerUID value, assuming the value to be 0")
|
logrus.WithError(err).Debug("failed to get the OwnerUID value, assuming the value to be 0")
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// default action is to start a container
|
// default action is to start a container
|
||||||
var runCommand = cli.Command{
|
var runCommand = &cli.Command{
|
||||||
Name: "run",
|
Name: "run",
|
||||||
Usage: "create and run a container",
|
Usage: "create and run a container",
|
||||||
ArgsUsage: `<container-id>
|
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
|
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
|
command(s) that get executed on start, edit the args parameter of the spec. See
|
||||||
"runc spec --help" for more explanation.`,
|
"runc spec --help" for more explanation.`,
|
||||||
|
// Disable comma as separator for slice flags.
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "bundle, b",
|
Name: "bundle",
|
||||||
|
Aliases: []string{"b"},
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "console-socket",
|
Name: "console-socket",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
|
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",
|
Name: "pidfd-socket",
|
||||||
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the init process",
|
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the init process",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "detach, d",
|
Name: "detach",
|
||||||
|
Aliases: []string{"d"},
|
||||||
Usage: "detach from the container's process",
|
Usage: "detach from the container's process",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "keep",
|
Name: "keep",
|
||||||
Usage: "do not delete the container after it exits",
|
Usage: "do not delete the container after it exits",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "pid-file",
|
Name: "pid-file",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "specify the file to write the process id to",
|
Usage: "specify the file to write the process id to",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "no-subreaper",
|
Name: "no-subreaper",
|
||||||
Usage: "disable the use of the subreaper used to reap reparented processes",
|
Usage: "disable the use of the subreaper used to reap reparented processes",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "no-pivot",
|
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",
|
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",
|
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",
|
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",
|
Name: "preserve-fds",
|
||||||
Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)",
|
Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
status, err := startContainer(context, CT_ACT_RUN, nil)
|
status, err := startContainer(cmd, CT_ACT_RUN, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// exit with the container's exit status so any external supervisor is
|
// exit with the container's exit status so any external supervisor is
|
||||||
// notified of the exit with the correct exit status.
|
// notified of the exit with the correct exit status.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -9,10 +10,10 @@ import (
|
|||||||
"github.com/opencontainers/runc/libcontainer/configs"
|
"github.com/opencontainers/runc/libcontainer/configs"
|
||||||
"github.com/opencontainers/runc/libcontainer/specconv"
|
"github.com/opencontainers/runc/libcontainer/specconv"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"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",
|
Name: "spec",
|
||||||
Usage: "create a new specification file",
|
Usage: "create a new specification file",
|
||||||
ArgsUsage: "",
|
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
|
Note that --rootless is not needed when you execute runc as the root in a user namespace
|
||||||
created by an unprivileged user.
|
created by an unprivileged user.
|
||||||
`,
|
`,
|
||||||
|
// Disable comma as separator for slice flags.
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "bundle, b",
|
Name: "bundle",
|
||||||
|
Aliases: []string{"b"},
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "path to the root of the bundle directory",
|
Usage: "path to the root of the bundle directory",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "rootless",
|
Name: "rootless",
|
||||||
Usage: "generate a configuration for a rootless container",
|
Usage: "generate a configuration for a rootless container",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||||
if err := checkArgs(context, 0, exactArgs); err != nil {
|
if err := checkArgs(cmd, 0, exactArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
spec := specconv.Example()
|
spec := specconv.Example()
|
||||||
|
|
||||||
rootless := context.Bool("rootless")
|
rootless := cmd.Bool("rootless")
|
||||||
if rootless {
|
if rootless {
|
||||||
specconv.ToRootless(spec)
|
specconv.ToRootless(spec)
|
||||||
}
|
}
|
||||||
@@ -96,7 +100,7 @@ created by an unprivileged user.
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
bundle := context.String("bundle")
|
bundle := cmd.String("bundle")
|
||||||
if bundle != "" {
|
if bundle != "" {
|
||||||
if err := os.Chdir(bundle); err != nil {
|
if err := os.Chdir(bundle); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer"
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var startCommand = cli.Command{
|
var startCommand = &cli.Command{
|
||||||
Name: "start",
|
Name: "start",
|
||||||
Usage: "executes the user defined process in a created container",
|
Usage: "executes the user defined process in a created container",
|
||||||
ArgsUsage: `<container-id>
|
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
|
are starting. The name you provide for the container instance must be unique on
|
||||||
your host.`,
|
your host.`,
|
||||||
Description: `The start command executes the user defined process in a created container.`,
|
Description: `The start command executes the user defined process in a created container.`,
|
||||||
Action: func(context *cli.Context) error {
|
// Disable comma as separator for slice flags.
|
||||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
DisableSliceFlagSeparator: true,
|
||||||
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||||
|
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
container, err := getContainer(context)
|
container, err := getContainer(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -32,7 +35,7 @@ your host.`,
|
|||||||
}
|
}
|
||||||
switch status {
|
switch status {
|
||||||
case libcontainer.Created:
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer"
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
"github.com/opencontainers/runc/libcontainer/utils"
|
"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",
|
Name: "state",
|
||||||
Usage: "output the state of a container",
|
Usage: "output the state of a container",
|
||||||
ArgsUsage: `<container-id>
|
ArgsUsage: `<container-id>
|
||||||
@@ -17,11 +18,13 @@ var stateCommand = cli.Command{
|
|||||||
Where "<container-id>" is your name for the instance of the container.`,
|
Where "<container-id>" is your name for the instance of the container.`,
|
||||||
Description: `The state command outputs current state information for the
|
Description: `The state command outputs current state information for the
|
||||||
instance of a container.`,
|
instance of a container.`,
|
||||||
Action: func(context *cli.Context) error {
|
// Disable comma as separator for slice flags.
|
||||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
DisableSliceFlagSeparator: true,
|
||||||
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||||
|
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
container, err := getContainer(context)
|
container, err := getContainer(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/opencontainers/runc/internal/cmsg"
|
"github.com/opencontainers/runc/internal/cmsg"
|
||||||
@@ -32,32 +33,32 @@ pidfd:
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := cli.NewApp()
|
app := &cli.Command{}
|
||||||
app.Name = "pidfd-kill"
|
app.Name = "pidfd-kill"
|
||||||
app.Usage = usage
|
app.Usage = usage
|
||||||
|
|
||||||
app.Flags = []cli.Flag{
|
app.Flags = []cli.Flag{
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "signal",
|
Name: "signal",
|
||||||
Value: "SIGKILL",
|
Value: "SIGKILL",
|
||||||
Usage: "Signal to send to the init process",
|
Usage: "Signal to send to the init process",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "pid-file",
|
Name: "pid-file",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "Path to write the pidfd-kill process ID to",
|
Usage: "Path to write the pidfd-kill process ID to",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Action = func(ctx *cli.Context) error {
|
app.Action = func(_ context.Context, cmd *cli.Command) error {
|
||||||
args := ctx.Args()
|
args := cmd.Args().Slice()
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return errors.New("required a single socket path")
|
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 != "" {
|
if pidFile != "" {
|
||||||
pid := fmt.Sprintf("%d\n", os.Getpid())
|
pid := fmt.Sprintf("%d\n", os.Getpid())
|
||||||
if err := os.WriteFile(pidFile, []byte(pid), 0o644); err != nil {
|
if err := os.WriteFile(pidFile, []byte(pid), 0o644); err != nil {
|
||||||
@@ -66,7 +67,7 @@ func main() {
|
|||||||
defer os.Remove(pidFile)
|
defer os.Remove(pidFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
sigStr := ctx.String("signal")
|
sigStr := cmd.String("signal")
|
||||||
if sigStr == "" {
|
if sigStr == "" {
|
||||||
sigStr = "SIGKILL"
|
sigStr = "SIGKILL"
|
||||||
}
|
}
|
||||||
@@ -84,7 +85,7 @@ func main() {
|
|||||||
|
|
||||||
return unix.PidfdSendSignal(int(pidfdFile.Fd()), sig, nil, 0)
|
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)
|
fmt.Fprintln(os.Stderr, "fatal error:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -34,7 +35,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
|
|
||||||
"github.com/opencontainers/runc/internal/cmsg"
|
"github.com/opencontainers/runc/internal/cmsg"
|
||||||
)
|
)
|
||||||
@@ -175,7 +176,7 @@ func handleNull(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := cli.NewApp()
|
app := &cli.Command{}
|
||||||
app.Name = "recvtty"
|
app.Name = "recvtty"
|
||||||
app.Usage = usage
|
app.Usage = usage
|
||||||
|
|
||||||
@@ -191,30 +192,31 @@ func main() {
|
|||||||
|
|
||||||
// Set the flags.
|
// Set the flags.
|
||||||
app.Flags = []cli.Flag{
|
app.Flags = []cli.Flag{
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "mode, m",
|
Name: "mode",
|
||||||
|
Aliases: []string{"m"},
|
||||||
Value: "single",
|
Value: "single",
|
||||||
Usage: "Mode of operation (single or null)",
|
Usage: "Mode of operation (single or null)",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "pid-file",
|
Name: "pid-file",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "Path to write daemon process ID to",
|
Usage: "Path to write daemon process ID to",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "no-stdin",
|
Name: "no-stdin",
|
||||||
Usage: "Disable stdin handling (no-op for null mode)",
|
Usage: "Disable stdin handling (no-op for null mode)",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Action = func(ctx *cli.Context) error {
|
app.Action = func(_ context.Context, cmd *cli.Command) error {
|
||||||
args := ctx.Args()
|
args := cmd.Args().Slice()
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return errors.New("need to specify a single socket path")
|
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 != "" {
|
if pidPath != "" {
|
||||||
pid := fmt.Sprintf("%d\n", os.Getpid())
|
pid := fmt.Sprintf("%d\n", os.Getpid())
|
||||||
if err := os.WriteFile(pidPath, []byte(pid), 0o644); err != nil {
|
if err := os.WriteFile(pidPath, []byte(pid), 0o644); err != nil {
|
||||||
@@ -222,8 +224,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
noStdin := ctx.Bool("no-stdin")
|
noStdin := cmd.Bool("no-stdin")
|
||||||
switch ctx.String("mode") {
|
switch cmd.String("mode") {
|
||||||
case "single":
|
case "single":
|
||||||
if err := handleSingle(path, noStdin); err != nil {
|
if err := handleSingle(path, noStdin); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -233,11 +235,11 @@ func main() {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
default:
|
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
|
return nil
|
||||||
}
|
}
|
||||||
if err := app.Run(os.Args); err != nil {
|
if err := app.Run(context.Background(), os.Args); err != nil {
|
||||||
bail(err)
|
bail(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -13,7 +14,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
|
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
)
|
)
|
||||||
@@ -102,12 +103,12 @@ func remapRootfs(root string, uidMap, gidMap []specs.LinuxIDMapping) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := cli.NewApp()
|
app := &cli.Command{}
|
||||||
app.Name = "remap-rootfs"
|
app.Name = "remap-rootfs"
|
||||||
app.Usage = usage
|
app.Usage = usage
|
||||||
|
|
||||||
app.Action = func(ctx *cli.Context) error {
|
app.Action = func(_ context.Context, cmd *cli.Command) error {
|
||||||
args := ctx.Args()
|
args := cmd.Args().Slice()
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return errors.New("exactly one bundle argument must be provided")
|
return errors.New("exactly one bundle argument must be provided")
|
||||||
}
|
}
|
||||||
@@ -141,7 +142,7 @@ func main() {
|
|||||||
|
|
||||||
return remapRootfs(rootfs, uidMap, gidMap)
|
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)
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -14,18 +15,21 @@ import (
|
|||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
"github.com/opencontainers/runc/libcontainer/intelrdt"
|
"github.com/opencontainers/runc/libcontainer/intelrdt"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"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 }
|
func mkPtr[T any](v T) *T { return &v }
|
||||||
|
|
||||||
var updateCommand = cli.Command{
|
var updateCommand = &cli.Command{
|
||||||
Name: "update",
|
Name: "update",
|
||||||
Usage: "update container resource constraints",
|
Usage: "update container resource constraints",
|
||||||
ArgsUsage: `<container-id>`,
|
ArgsUsage: `<container-id>`,
|
||||||
|
// Disable comma as separator for slice flags.
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "resources, r",
|
Name: "resources",
|
||||||
|
Aliases: []string{"r"},
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: `path to the file containing the resources to update or '-' to read from the standard input
|
Usage: `path to the file containing the resources to update or '-' to read from the standard input
|
||||||
|
|
||||||
@@ -59,86 +63,86 @@ other options are ignored.
|
|||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
|
||||||
cli.IntFlag{
|
&cli.IntFlag{
|
||||||
Name: "blkio-weight",
|
Name: "blkio-weight",
|
||||||
Usage: "Specifies per cgroup weight, range is from 10 to 1000",
|
Usage: "Specifies per cgroup weight, range is from 10 to 1000",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "cpu-period",
|
Name: "cpu-period",
|
||||||
Usage: "CPU CFS period to be used for hardcapping (in usecs). 0 to use system default",
|
Usage: "CPU CFS period to be used for hardcapping (in usecs). 0 to use system default",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "cpu-quota",
|
Name: "cpu-quota",
|
||||||
Usage: "CPU CFS hardcap limit (in usecs). Allowed cpu time in a given period",
|
Usage: "CPU CFS hardcap limit (in usecs). Allowed cpu time in a given period",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "cpu-burst",
|
Name: "cpu-burst",
|
||||||
Usage: "CPU CFS hardcap burst limit (in usecs). Allowed accumulated cpu time additionally for burst a given period",
|
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",
|
Name: "cpu-share",
|
||||||
Usage: "CPU shares (relative weight vs. other containers)",
|
Usage: "CPU shares (relative weight vs. other containers)",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "cpu-rt-period",
|
Name: "cpu-rt-period",
|
||||||
Usage: "CPU realtime period to be used for hardcapping (in usecs). 0 to use system default",
|
Usage: "CPU realtime period to be used for hardcapping (in usecs). 0 to use system default",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "cpu-rt-runtime",
|
Name: "cpu-rt-runtime",
|
||||||
Usage: "CPU realtime hardcap limit (in usecs). Allowed cpu time in a given period",
|
Usage: "CPU realtime hardcap limit (in usecs). Allowed cpu time in a given period",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "cpuset-cpus",
|
Name: "cpuset-cpus",
|
||||||
Usage: "CPU(s) to use",
|
Usage: "CPU(s) to use",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "cpuset-mems",
|
Name: "cpuset-mems",
|
||||||
Usage: "Memory node(s) to use",
|
Usage: "Memory node(s) to use",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "kernel-memory",
|
Name: "kernel-memory",
|
||||||
Usage: "(obsoleted; do not use)",
|
Usage: "(obsoleted; do not use)",
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "kernel-memory-tcp",
|
Name: "kernel-memory-tcp",
|
||||||
Usage: "(obsoleted; do not use)",
|
Usage: "(obsoleted; do not use)",
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "memory",
|
Name: "memory",
|
||||||
Usage: "Memory limit (in bytes)",
|
Usage: "Memory limit (in bytes)",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "cpu-idle",
|
Name: "cpu-idle",
|
||||||
Usage: "set cgroup SCHED_IDLE or not, 0: default behavior, 1: SCHED_IDLE",
|
Usage: "set cgroup SCHED_IDLE or not, 0: default behavior, 1: SCHED_IDLE",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "memory-reservation",
|
Name: "memory-reservation",
|
||||||
Usage: "Memory reservation or soft_limit (in bytes)",
|
Usage: "Memory reservation or soft_limit (in bytes)",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "memory-swap",
|
Name: "memory-swap",
|
||||||
Usage: "Total memory usage (memory + swap); set '-1' to enable unlimited swap",
|
Usage: "Total memory usage (memory + swap); set '-1' to enable unlimited swap",
|
||||||
},
|
},
|
||||||
cli.IntFlag{
|
&cli.IntFlag{
|
||||||
Name: "pids-limit",
|
Name: "pids-limit",
|
||||||
Usage: "Maximum number of pids allowed in the container",
|
Usage: "Maximum number of pids allowed in the container",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "l3-cache-schema",
|
Name: "l3-cache-schema",
|
||||||
Usage: "The string of Intel RDT/CAT L3 cache schema",
|
Usage: "The string of Intel RDT/CAT L3 cache schema",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "mem-bw-schema",
|
Name: "mem-bw-schema",
|
||||||
Usage: "The string of Intel RDT/MBA memory bandwidth schema",
|
Usage: "The string of Intel RDT/MBA memory bandwidth schema",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||||
if err := checkArgs(context, 1, exactArgs); err != nil {
|
if err := checkArgs(cmd, 1, exactArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
container, err := getContainer(context)
|
container, err := getContainer(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -155,7 +159,7 @@ other options are ignored.
|
|||||||
|
|
||||||
config := container.Config()
|
config := container.Config()
|
||||||
|
|
||||||
if in := context.String("resources"); in != "" {
|
if in := cmd.String("resources"); in != "" {
|
||||||
var (
|
var (
|
||||||
f *os.File
|
f *os.File
|
||||||
err error
|
err error
|
||||||
@@ -175,16 +179,16 @@ other options are ignored.
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if val := context.Int("blkio-weight"); val != 0 {
|
if val := cmd.Int("blkio-weight"); val != 0 {
|
||||||
r.BlockIO.Weight = mkPtr(uint16(val))
|
r.BlockIO.Weight = mkPtr(uint16(val))
|
||||||
}
|
}
|
||||||
if val := context.String("cpuset-cpus"); val != "" {
|
if val := cmd.String("cpuset-cpus"); val != "" {
|
||||||
r.CPU.Cpus = val
|
r.CPU.Cpus = val
|
||||||
}
|
}
|
||||||
if val := context.String("cpuset-mems"); val != "" {
|
if val := cmd.String("cpuset-mems"); val != "" {
|
||||||
r.CPU.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)
|
idle, err := strconv.ParseInt(val, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid value for cpu-idle: %w", err)
|
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-rt-period", &r.CPU.RealtimePeriod},
|
||||||
{"cpu-share", &r.CPU.Shares},
|
{"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)
|
v, err := strconv.ParseUint(val, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid value for %s: %w", pair.opt, err)
|
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-quota", &r.CPU.Quota},
|
||||||
{"cpu-rt-runtime", &r.CPU.RealtimeRuntime},
|
{"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)
|
v, err := strconv.ParseInt(val, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid value for %s: %w", pair.opt, err)
|
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},
|
{"kernel-memory-tcp", &r.Memory.KernelTCP},
|
||||||
{"memory-reservation", &r.Memory.Reservation},
|
{"memory-reservation", &r.Memory.Reservation},
|
||||||
} {
|
} {
|
||||||
if val := context.String(pair.opt); val != "" {
|
if val := cmd.String(pair.opt); val != "" {
|
||||||
var v int64
|
var v int64
|
||||||
|
|
||||||
if val != "-1" {
|
if val != "-1" {
|
||||||
@@ -249,8 +253,8 @@ other options are ignored.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.IsSet("pids-limit") {
|
if cmd.IsSet("pids-limit") {
|
||||||
r.Pids.Limit = mkPtr(int64(context.Int("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
|
config.Cgroups.Resources.Unified = r.Unified
|
||||||
|
|
||||||
// Update Intel RDT
|
// Update Intel RDT
|
||||||
l3CacheSchema := context.String("l3-cache-schema")
|
l3CacheSchema := cmd.String("l3-cache-schema")
|
||||||
memBwSchema := context.String("mem-bw-schema")
|
memBwSchema := cmd.String("mem-bw-schema")
|
||||||
if l3CacheSchema != "" && !intelrdt.IsCATEnabled() {
|
if l3CacheSchema != "" && !intelrdt.IsCATEnabled() {
|
||||||
return errors.New("Intel RDT/CAT: l3 cache schema is not enabled")
|
return errors.New("Intel RDT/CAT: l3 cache schema is not enabled")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -11,7 +12,7 @@ import (
|
|||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -20,27 +21,27 @@ const (
|
|||||||
maxArgs
|
maxArgs
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkArgs(context *cli.Context, expected, checkType int) error {
|
func checkArgs(cmd *cli.Command, expected, checkType int) error {
|
||||||
var err error
|
var err error
|
||||||
cmdName := context.Command.Name
|
cmdName := cmd.Name
|
||||||
switch checkType {
|
switch checkType {
|
||||||
case exactArgs:
|
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)
|
err = fmt.Errorf("%s: %q requires exactly %d argument(s)", os.Args[0], cmdName, expected)
|
||||||
}
|
}
|
||||||
case minArgs:
|
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)
|
err = fmt.Errorf("%s: %q requires a minimum of %d argument(s)", os.Args[0], cmdName, expected)
|
||||||
}
|
}
|
||||||
case maxArgs:
|
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)
|
err = fmt.Errorf("%s: %q requires a maximum of %d argument(s)", os.Args[0], cmdName, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Incorrect Usage.\n\n")
|
fmt.Printf("Incorrect Usage.\n\n")
|
||||||
_ = cli.ShowCommandHelp(context, cmdName)
|
_ = cli.ShowCommandHelp(context.Background(), cmd, cmdName)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -68,8 +69,8 @@ func fatalWithCode(err error, ret int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setupSpec performs initial setup based on the cli.Context for the container
|
// setupSpec performs initial setup based on the cli.Context for the container
|
||||||
func setupSpec(context *cli.Context) (*specs.Spec, error) {
|
func setupSpec(cmd *cli.Command) (*specs.Spec, error) {
|
||||||
bundle := context.String("bundle")
|
bundle := cmd.String("bundle")
|
||||||
if bundle != "" {
|
if bundle != "" {
|
||||||
if err := os.Chdir(bundle); err != nil {
|
if err := os.Chdir(bundle); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -82,8 +83,8 @@ func setupSpec(context *cli.Context) (*specs.Spec, error) {
|
|||||||
return spec, nil
|
return spec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func revisePidFile(context *cli.Context) error {
|
func revisePidFile(cmd *cli.Command) error {
|
||||||
pidFile := context.String("pid-file")
|
pidFile := cmd.String("pid-file")
|
||||||
if pidFile == "" {
|
if pidFile == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -94,17 +95,17 @@ func revisePidFile(context *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return context.Set("pid-file", pidFile)
|
return cmd.Set("pid-file", pidFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// reviseRootDir ensures that the --root option argument,
|
// reviseRootDir ensures that the --root option argument,
|
||||||
// if specified, is converted to an absolute and cleaned path,
|
// if specified, is converted to an absolute and cleaned path,
|
||||||
// and that this path is sane.
|
// and that this path is sane.
|
||||||
func reviseRootDir(context *cli.Context) error {
|
func reviseRootDir(cmd *cli.Command) error {
|
||||||
if !context.IsSet("root") {
|
if !cmd.IsSet("root") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
root, err := filepath.Abs(context.GlobalString("root"))
|
root, err := filepath.Abs(cmd.String("root"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -117,7 +118,7 @@ func reviseRootDir(context *cli.Context) error {
|
|||||||
return errors.New("Option --root argument should not be set to /")
|
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"
|
// parseBoolOrAuto returns (nil, nil) if s is empty or "auto"
|
||||||
|
|||||||
+23
-23
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
selinux "github.com/opencontainers/selinux/go-selinux"
|
selinux "github.com/opencontainers/selinux/go-selinux"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/opencontainers/runc/internal/pathrs"
|
"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
|
// getContainer returns the specified container instance by loading it from
|
||||||
// a state directory (root).
|
// a state directory (root).
|
||||||
func getContainer(context *cli.Context) (*libcontainer.Container, error) {
|
func getContainer(cmd *cli.Command) (*libcontainer.Container, error) {
|
||||||
id := context.Args().First()
|
id := cmd.Args().First()
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return nil, errEmptyID
|
return nil, errEmptyID
|
||||||
}
|
}
|
||||||
root := context.GlobalString("root")
|
root := cmd.String("root")
|
||||||
return libcontainer.Load(root, id)
|
return libcontainer.Load(root, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,16 +181,16 @@ func createPidFile(path string, process *libcontainer.Process) error {
|
|||||||
return os.Rename(tmpName, path)
|
return os.Rename(tmpName, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createContainer(context *cli.Context, id string, spec *specs.Spec) (*libcontainer.Container, error) {
|
func createContainer(cmd *cli.Command, id string, spec *specs.Spec) (*libcontainer.Container, error) {
|
||||||
rootlessCg, err := shouldUseRootlessCgroupManager(context)
|
rootlessCg, err := shouldUseRootlessCgroupManager(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
config, err := specconv.CreateLibcontainerConfig(&specconv.CreateOpts{
|
config, err := specconv.CreateLibcontainerConfig(&specconv.CreateOpts{
|
||||||
CgroupName: id,
|
CgroupName: id,
|
||||||
UseSystemdCgroup: context.GlobalBool("systemd-cgroup"),
|
UseSystemdCgroup: cmd.Bool("systemd-cgroup"),
|
||||||
NoPivotRoot: context.Bool("no-pivot"),
|
NoPivotRoot: cmd.Bool("no-pivot"),
|
||||||
NoNewKeyring: context.Bool("no-new-keyring"),
|
NoNewKeyring: cmd.Bool("no-new-keyring"),
|
||||||
Spec: spec,
|
Spec: spec,
|
||||||
RootlessEUID: os.Geteuid() != 0,
|
RootlessEUID: os.Geteuid() != 0,
|
||||||
RootlessCgroups: rootlessCg,
|
RootlessCgroups: rootlessCg,
|
||||||
@@ -199,7 +199,7 @@ func createContainer(context *cli.Context, id string, spec *specs.Spec) (*libcon
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
root := context.GlobalString("root")
|
root := cmd.String("root")
|
||||||
return libcontainer.Create(root, id, config)
|
return libcontainer.Create(root, id, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,26 +378,26 @@ const (
|
|||||||
CT_ACT_RESTORE
|
CT_ACT_RESTORE
|
||||||
)
|
)
|
||||||
|
|
||||||
func startContainer(context *cli.Context, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) {
|
func startContainer(cmd *cli.Command, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) {
|
||||||
if err := revisePidFile(context); err != nil {
|
if err := revisePidFile(cmd); err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
spec, err := setupSpec(context)
|
spec, err := setupSpec(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
id := context.Args().First()
|
id := cmd.Args().First()
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return -1, errEmptyID
|
return -1, errEmptyID
|
||||||
}
|
}
|
||||||
|
|
||||||
notifySocket := newNotifySocket(context, os.Getenv("NOTIFY_SOCKET"), id)
|
notifySocket := newNotifySocket(cmd, os.Getenv("NOTIFY_SOCKET"), id)
|
||||||
if notifySocket != nil {
|
if notifySocket != nil {
|
||||||
notifySocket.setupSpec(spec)
|
notifySocket.setupSpec(spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
container, err := createContainer(context, id, spec)
|
container, err := createContainer(cmd, id, spec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
@@ -414,16 +414,16 @@ func startContainer(context *cli.Context, action CtAct, criuOpts *libcontainer.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
r := &runner{
|
r := &runner{
|
||||||
enableSubreaper: !context.Bool("no-subreaper"),
|
enableSubreaper: !cmd.Bool("no-subreaper"),
|
||||||
shouldDestroy: !context.Bool("keep"),
|
shouldDestroy: !cmd.Bool("keep"),
|
||||||
container: container,
|
container: container,
|
||||||
listenFDs: activation.Files(), // On-demand socket activation.
|
listenFDs: activation.Files(), // On-demand socket activation.
|
||||||
notifySocket: notifySocket,
|
notifySocket: notifySocket,
|
||||||
consoleSocket: context.String("console-socket"),
|
consoleSocket: cmd.String("console-socket"),
|
||||||
pidfdSocket: context.String("pidfd-socket"),
|
pidfdSocket: cmd.String("pidfd-socket"),
|
||||||
detach: context.Bool("detach"),
|
detach: cmd.Bool("detach"),
|
||||||
pidFile: context.String("pid-file"),
|
pidFile: cmd.String("pid-file"),
|
||||||
preserveFDs: context.Int("preserve-fds"),
|
preserveFDs: cmd.Int("preserve-fds"),
|
||||||
action: action,
|
action: action,
|
||||||
criuOpts: criuOpts,
|
criuOpts: criuOpts,
|
||||||
init: true,
|
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
|
*.coverprofile
|
||||||
coverage.txt
|
*.exe
|
||||||
node_modules/
|
*.orig
|
||||||
vendor
|
.*envrc
|
||||||
|
.envrc
|
||||||
.idea
|
.idea
|
||||||
/.local/
|
/.local/
|
||||||
/internal/
|
|
||||||
/site/
|
/site/
|
||||||
package.json
|
coverage.txt
|
||||||
package-lock.json
|
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
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be
|
reported by contacting urfave-governance@googlegroups.com, a members-only group
|
||||||
reviewed and investigated and will result in a response that is deemed necessary
|
that is world-postable. All complaints will be reviewed and investigated and
|
||||||
and appropriate to the circumstances. The project team is obligated to maintain
|
will result in a response that is deemed necessary and appropriate to the
|
||||||
confidentiality with regard to the reporter of an incident. Further details of
|
circumstances. The project team is obligated to maintain confidentiality with
|
||||||
specific enforcement policies may be posted separately.
|
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
|
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
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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