Initial implementation of arca, a LUKS volume manager.
Go CLI using cobra with mount, unmount, status, and init subcommands. Unlocks via udisks2 D-Bus (passphrase/keyfile) or cryptsetup (FIDO2/TPM2) with ordered method fallback. Includes NixOS-specific LD_LIBRARY_PATH injection for systemd cryptsetup token plugins. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
168
PLAN.md
Normal file
168
PLAN.md
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
# arca — Implementation Plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
A Go CLI that manages LUKS volumes. Uses udisks2 D-Bus for
|
||||||
|
passphrase/keyfile unlock (no root needed), and falls back to
|
||||||
|
`cryptsetup` for FIDO2/TPM2 tokens (requires root/doas). Three
|
||||||
|
subcommands: `mount`, `unmount`, `status`.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- `github.com/spf13/cobra` — CLI framework
|
||||||
|
- `github.com/godbus/dbus/v5` — D-Bus client
|
||||||
|
- `gopkg.in/yaml.v3` — config file parsing
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
arca/
|
||||||
|
├── main.go
|
||||||
|
├── cmd/
|
||||||
|
│ ├── root.go # root command, global flags
|
||||||
|
│ ├── mount.go # mount subcommand
|
||||||
|
│ ├── unmount.go # unmount subcommand
|
||||||
|
│ └── status.go # status subcommand
|
||||||
|
├── internal/
|
||||||
|
│ ├── udisks/ # udisks2 D-Bus client
|
||||||
|
│ │ ├── client.go # connection + device discovery
|
||||||
|
│ │ ├── encrypt.go # Unlock / Lock operations
|
||||||
|
│ │ ├── mount.go # Mount / Unmount operations
|
||||||
|
│ │ └── types.go # object path helpers, constants
|
||||||
|
│ ├── cryptsetup/ # cryptsetup CLI wrapper (FIDO2/TPM2)
|
||||||
|
│ │ └── cryptsetup.go
|
||||||
|
│ ├── unlock/ # method sequencing logic
|
||||||
|
│ │ └── unlock.go
|
||||||
|
│ └── config/
|
||||||
|
│ └── config.go # YAML config loading + device resolution
|
||||||
|
├── flake.nix
|
||||||
|
├── go.mod
|
||||||
|
├── go.sum
|
||||||
|
├── README.md
|
||||||
|
└── PLAN.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 1: Core Unlock/Lock Backend
|
||||||
|
|
||||||
|
Build `internal/udisks/` for D-Bus operations and `internal/cryptsetup/`
|
||||||
|
for FIDO2/TPM2 fallback.
|
||||||
|
|
||||||
|
### udisks2 D-Bus (passphrase + keyfile)
|
||||||
|
|
||||||
|
1. Connect to the system bus.
|
||||||
|
2. Enumerate block devices under `/org/freedesktop/UDisks2/block_devices/`.
|
||||||
|
3. Find a block device by device path or UUID.
|
||||||
|
4. `Unlock(passphrase)` — call `org.freedesktop.UDisks2.Encrypted.Unlock`
|
||||||
|
on the LUKS partition. Returns the cleartext device object path.
|
||||||
|
5. `Mount()` — call `org.freedesktop.UDisks2.Filesystem.Mount` on the
|
||||||
|
cleartext device. Returns the mount point path.
|
||||||
|
6. `Unmount()` + `Lock()` — reverse operations.
|
||||||
|
|
||||||
|
Key D-Bus details:
|
||||||
|
- Bus name: `org.freedesktop.UDisks2`
|
||||||
|
- Object paths: `/org/freedesktop/UDisks2/block_devices/<name>`
|
||||||
|
- Interfaces: `org.freedesktop.UDisks2.Encrypted` (Unlock, Lock),
|
||||||
|
`org.freedesktop.UDisks2.Filesystem` (Mount, Unmount),
|
||||||
|
`org.freedesktop.UDisks2.Block` (device properties, UUID lookup)
|
||||||
|
|
||||||
|
### cryptsetup fallback (FIDO2 + TPM2)
|
||||||
|
|
||||||
|
udisks2 does not support FIDO2/TPM2 token-based keyslots. For these,
|
||||||
|
fall back to invoking `cryptsetup` (via doas/sudo):
|
||||||
|
|
||||||
|
- FIDO2: `cryptsetup open --token-type systemd-fido2 <device> <name>`
|
||||||
|
- TPM2: `cryptsetup open --token-type systemd-tpm2 <device> <name>`
|
||||||
|
- `--token-only` can attempt all enrolled tokens automatically.
|
||||||
|
|
||||||
|
After `cryptsetup open`, mount via udisks2 D-Bus or plain `mount`.
|
||||||
|
|
||||||
|
### Unlock method sequencing
|
||||||
|
|
||||||
|
Each device has an ordered list of methods to try. The unlock logic walks
|
||||||
|
the list in order, stopping at the first success:
|
||||||
|
|
||||||
|
1. Try the first method (e.g., `fido2`).
|
||||||
|
2. If it fails (device not plugged in, timeout, etc.), try the next.
|
||||||
|
3. Continue until one succeeds or all are exhausted.
|
||||||
|
|
||||||
|
Default when no config exists: `[passphrase]`.
|
||||||
|
|
||||||
|
## Phase 2: Config File
|
||||||
|
|
||||||
|
Build `internal/config/`:
|
||||||
|
|
||||||
|
1. Load `~/.config/arca/config.yaml` (respect `$XDG_CONFIG_HOME`).
|
||||||
|
2. Resolve an alias to a UUID and unlock methods.
|
||||||
|
3. If no config file exists, that's fine — device path arguments still work
|
||||||
|
with the default method sequence `[passphrase]`.
|
||||||
|
|
||||||
|
Config schema:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
devices:
|
||||||
|
backup:
|
||||||
|
uuid: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
|
mountpoint: "/mnt/backup" # optional
|
||||||
|
methods: # optional, default: [passphrase]
|
||||||
|
- fido2
|
||||||
|
- passphrase
|
||||||
|
media:
|
||||||
|
uuid: "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
|
||||||
|
methods:
|
||||||
|
- keyfile
|
||||||
|
- passphrase
|
||||||
|
keyfile: "/path/to/media.key" # required if keyfile is in methods
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported methods: `passphrase`, `keyfile`, `fido2`, `tpm2`.
|
||||||
|
|
||||||
|
## Phase 3: CLI Commands
|
||||||
|
|
||||||
|
Wire up cobra subcommands:
|
||||||
|
|
||||||
|
### `arca mount <device|alias>`
|
||||||
|
|
||||||
|
1. Resolve argument: if it matches a config alias, look up UUID and
|
||||||
|
methods list; otherwise treat as a device path with default methods.
|
||||||
|
2. Find the block device via udisks2.
|
||||||
|
3. Walk the methods list in order, attempting each unlock strategy:
|
||||||
|
- `passphrase` / `keyfile`: via udisks2 D-Bus (no root).
|
||||||
|
- `fido2` / `tpm2`: via `cryptsetup` CLI (requires doas/sudo).
|
||||||
|
- Stop at the first success; if all fail, report each error.
|
||||||
|
4. Mount the resulting cleartext device via udisks2.
|
||||||
|
5. Print the mount point.
|
||||||
|
|
||||||
|
### `arca unmount <device|alias>`
|
||||||
|
|
||||||
|
1. Resolve argument to the LUKS block device.
|
||||||
|
2. Find the associated cleartext (mapped) device.
|
||||||
|
3. Call Unmount on the cleartext device.
|
||||||
|
4. Call Lock on the LUKS device.
|
||||||
|
|
||||||
|
### `arca status`
|
||||||
|
|
||||||
|
1. Enumerate all block devices with the Encrypted interface.
|
||||||
|
2. For each, check if unlocked (has a cleartext device).
|
||||||
|
3. If mounted, show mount point.
|
||||||
|
4. Print a table: device, UUID, alias (if configured), state, mount point.
|
||||||
|
|
||||||
|
## Phase 4: Credential Input
|
||||||
|
|
||||||
|
- **Passphrase**: read from terminal with echo disabled (`golang.org/x/term`).
|
||||||
|
Alternatively, accept from stdin for scripting.
|
||||||
|
- **Keyfile**: read path from config, pass contents to udisks2.
|
||||||
|
- **FIDO2**: `cryptsetup` handles the token interaction (tap prompt comes
|
||||||
|
from libfido2).
|
||||||
|
- **TPM2**: `cryptsetup` handles TPM unsealing automatically.
|
||||||
|
|
||||||
|
## Phase 5: Packaging
|
||||||
|
|
||||||
|
- `flake.nix` with `buildGoModule`.
|
||||||
|
- Add as a flake input to the NixOS config repo.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- Should `mount` accept a `--mountpoint` flag to override the default?
|
||||||
|
- Should there be a `--dry-run` flag that shows what D-Bus calls would be
|
||||||
|
made?
|
||||||
|
- Is there value in a `--json` output flag for `status`?
|
||||||
69
README.md
Normal file
69
README.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# arca
|
||||||
|
|
||||||
|
A CLI tool for mounting and unmounting LUKS-encrypted volumes. Latin for
|
||||||
|
"strongbox."
|
||||||
|
|
||||||
|
arca talks to udisks2 over D-Bus, so no root privileges are required. It
|
||||||
|
handles the unlock-then-mount and unmount-then-lock sequences as single
|
||||||
|
commands.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
arca mount /dev/sda1 # unlock + mount by device path
|
||||||
|
arca mount backup # unlock + mount by config alias
|
||||||
|
arca unmount backup # unmount + lock
|
||||||
|
arca status # show unlocked LUKS volumes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Optional. Without a config file, arca works with device paths directly.
|
||||||
|
|
||||||
|
`~/.config/arca/config.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
devices:
|
||||||
|
backup:
|
||||||
|
uuid: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
|
mountpoint: "/mnt/backup" # optional — udisks2 picks if omitted
|
||||||
|
methods: # optional — default: [passphrase]
|
||||||
|
- fido2
|
||||||
|
- passphrase
|
||||||
|
media:
|
||||||
|
uuid: "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
|
||||||
|
methods:
|
||||||
|
- keyfile
|
||||||
|
- passphrase
|
||||||
|
keyfile: "/path/to/media.key"
|
||||||
|
```
|
||||||
|
|
||||||
|
Aliases let you refer to devices by name and ensure stable identification
|
||||||
|
via UUID regardless of device path changes.
|
||||||
|
|
||||||
|
The `methods` list defines the unlock strategies to try in order. If the
|
||||||
|
first method fails (e.g., FIDO2 key not present), arca tries the next.
|
||||||
|
Supported methods: `passphrase`, `keyfile`, `fido2`, `tpm2`.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Nix flake
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# flake.nix
|
||||||
|
inputs.arca.url = "git+https://git.wntrmute.dev/kyle/arca";
|
||||||
|
|
||||||
|
# in your NixOS config or home packages
|
||||||
|
environment.systemPackages = [ inputs.arca.packages.${system}.default ];
|
||||||
|
```
|
||||||
|
|
||||||
|
### From source
|
||||||
|
|
||||||
|
```
|
||||||
|
go install git.wntrmute.dev/kyle/arca@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Linux with udisks2 running (standard on most desktop distributions)
|
||||||
|
- D-Bus session or system bus access
|
||||||
108
cmd/init.go
Normal file
108
cmd/init.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/arca/internal/config"
|
||||||
|
"git.wntrmute.dev/kyle/arca/internal/udisks"
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var forceInit bool
|
||||||
|
|
||||||
|
var initCmd = &cobra.Command{
|
||||||
|
Use: "init",
|
||||||
|
Short: "Generate config from detected LUKS devices",
|
||||||
|
Long: "Scans for LUKS-encrypted devices, excludes the root filesystem, and writes a config file with passphrase as the default unlock method.",
|
||||||
|
RunE: runInit,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
initCmd.Flags().BoolVarP(&forceInit, "force", "f", false, "overwrite existing config file")
|
||||||
|
rootCmd.AddCommand(initCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runInit(cmd *cobra.Command, args []string) error {
|
||||||
|
cfgPath := config.Path()
|
||||||
|
|
||||||
|
if !forceInit {
|
||||||
|
if _, err := os.Stat(cfgPath); err == nil {
|
||||||
|
return fmt.Errorf("config already exists at %s (use --force to overwrite)", cfgPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := udisks.NewClient()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connecting to udisks2: %w", err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
encrypted, err := client.ListEncryptedDevices()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rootBacking, err := client.RootBackingDevices()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("detecting root device: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := config.Config{
|
||||||
|
Devices: make(map[string]config.DeviceConfig),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dev := range encrypted {
|
||||||
|
if isRootBacking(dev.ObjectPath, rootBacking) {
|
||||||
|
fmt.Fprintf(os.Stderr, "Skipping %s (root filesystem)\n", dev.DevicePath)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
alias := aliasFromPath(dev.DevicePath)
|
||||||
|
cfg.Devices[alias] = config.DeviceConfig{
|
||||||
|
UUID: dev.UUID,
|
||||||
|
Methods: []string{"passphrase"},
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "Found %s (UUID %s) -> alias %q\n", dev.DevicePath, dev.UUID, alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.Devices) == 0 {
|
||||||
|
fmt.Println("No non-root LUKS devices found.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := yaml.Marshal(&cfg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshaling config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(cfgPath), 0o755); err != nil {
|
||||||
|
return fmt.Errorf("creating config directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(cfgPath, data, 0o644); err != nil {
|
||||||
|
return fmt.Errorf("writing config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Config written to %s\n", cfgPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRootBacking(path dbus.ObjectPath, rootDevices []dbus.ObjectPath) bool {
|
||||||
|
for _, r := range rootDevices {
|
||||||
|
if path == r {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func aliasFromPath(devPath string) string {
|
||||||
|
// "/dev/sda1" -> "sda1", "/dev/nvme0n1p2" -> "nvme0n1p2"
|
||||||
|
base := filepath.Base(devPath)
|
||||||
|
return strings.TrimPrefix(base, "dm-")
|
||||||
|
}
|
||||||
75
cmd/mount.go
Normal file
75
cmd/mount.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/arca/internal/config"
|
||||||
|
"git.wntrmute.dev/kyle/arca/internal/cryptsetup"
|
||||||
|
"git.wntrmute.dev/kyle/arca/internal/udisks"
|
||||||
|
"git.wntrmute.dev/kyle/arca/internal/unlock"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mountCmd = &cobra.Command{
|
||||||
|
Use: "mount <device|alias>",
|
||||||
|
Short: "Unlock and mount a LUKS volume",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: runMount,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(mountCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMount(cmd *cobra.Command, args []string) error {
|
||||||
|
cfg := config.Load()
|
||||||
|
target := args[0]
|
||||||
|
|
||||||
|
client, err := udisks.NewClient()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connecting to udisks2: %w", err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
devCfg := cfg.ResolveDevice(target)
|
||||||
|
|
||||||
|
dev, err := client.FindDevice(devCfg.UUID, target)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
u := unlock.New(client, unlock.Options{
|
||||||
|
ReadPassphrase: readPassphrase,
|
||||||
|
KeyfilePath: devCfg.Keyfile,
|
||||||
|
})
|
||||||
|
|
||||||
|
result, err := u.Unlock(dev, devCfg.Methods)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var mountpoint string
|
||||||
|
if result.Privileged {
|
||||||
|
mountpoint, err = cryptsetup.Mount(result.Device.DevicePath, devCfg.Mountpoint)
|
||||||
|
} else {
|
||||||
|
mountpoint, err = client.Mount(result.Device)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("mounting: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(mountpoint)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPassphrase() (string, error) {
|
||||||
|
fmt.Fprint(os.Stderr, "Passphrase: ")
|
||||||
|
pass, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||||
|
fmt.Fprintln(os.Stderr)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("reading passphrase: %w", err)
|
||||||
|
}
|
||||||
|
return string(pass), nil
|
||||||
|
}
|
||||||
20
cmd/root.go
Normal file
20
cmd/root.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "arca",
|
||||||
|
Short: "Mount and unmount LUKS-encrypted volumes",
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
59
cmd/status.go
Normal file
59
cmd/status.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/arca/internal/config"
|
||||||
|
"git.wntrmute.dev/kyle/arca/internal/udisks"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var statusCmd = &cobra.Command{
|
||||||
|
Use: "status",
|
||||||
|
Short: "Show LUKS volume status",
|
||||||
|
RunE: runStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(statusCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runStatus(cmd *cobra.Command, args []string) error {
|
||||||
|
cfg := config.Load()
|
||||||
|
|
||||||
|
client, err := udisks.NewClient()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connecting to udisks2: %w", err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
devices, err := client.ListEncryptedDevices()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
|
||||||
|
fmt.Fprintln(w, "DEVICE\tUUID\tALIAS\tSTATE\tMOUNTPOINT")
|
||||||
|
|
||||||
|
for _, dev := range devices {
|
||||||
|
alias := cfg.AliasFor(dev.UUID)
|
||||||
|
state := "locked"
|
||||||
|
mountpoint := ""
|
||||||
|
|
||||||
|
if ct, err := client.CleartextDevice(&dev); err == nil {
|
||||||
|
state = "unlocked"
|
||||||
|
if mp, err := client.MountPoint(ct); err == nil && mp != "" {
|
||||||
|
mountpoint = mp
|
||||||
|
state = "mounted"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
|
||||||
|
dev.DevicePath, dev.UUID, alias, state, mountpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Flush()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
73
cmd/unmount.go
Normal file
73
cmd/unmount.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/arca/internal/config"
|
||||||
|
"git.wntrmute.dev/kyle/arca/internal/cryptsetup"
|
||||||
|
"git.wntrmute.dev/kyle/arca/internal/udisks"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var unmountCmd = &cobra.Command{
|
||||||
|
Use: "unmount <device|alias>",
|
||||||
|
Aliases: []string{"umount"},
|
||||||
|
Short: "Unmount and lock a LUKS volume",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: runUnmount,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(unmountCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runUnmount(cmd *cobra.Command, args []string) error {
|
||||||
|
cfg := config.Load()
|
||||||
|
target := args[0]
|
||||||
|
|
||||||
|
client, err := udisks.NewClient()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connecting to udisks2: %w", err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
devCfg := cfg.ResolveDevice(target)
|
||||||
|
|
||||||
|
dev, err := client.FindDevice(devCfg.UUID, target)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cleartext, err := client.CleartextDevice(dev)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("finding cleartext device: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmount: try udisks2 first, fall back to privileged umount.
|
||||||
|
mp, _ := client.MountPoint(cleartext)
|
||||||
|
if err := client.Unmount(cleartext); err != nil {
|
||||||
|
if mp == "" {
|
||||||
|
return fmt.Errorf("unmounting: %w", err)
|
||||||
|
}
|
||||||
|
if err := cryptsetup.Unmount(mp); err != nil {
|
||||||
|
return fmt.Errorf("unmounting: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock: try udisks2 first, fall back to cryptsetup close if it's
|
||||||
|
// an arca-managed mapping.
|
||||||
|
if err := client.Lock(dev); err != nil {
|
||||||
|
mapperName := cryptsetup.MapperName(dev.DevicePath)
|
||||||
|
if strings.HasPrefix(mapperName, "arca-") {
|
||||||
|
if err := cryptsetup.Close(mapperName); err != nil {
|
||||||
|
return fmt.Errorf("locking: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("locking: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Unmounted and locked %s\n", target)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
22
flake.nix
Normal file
22
flake.nix
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
description = "arca - LUKS volume manager";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
{ self, nixpkgs }:
|
||||||
|
let
|
||||||
|
system = "x86_64-linux";
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
packages.${system}.default = pkgs.buildGoModule {
|
||||||
|
pname = "arca";
|
||||||
|
version = "0.1.0";
|
||||||
|
src = ./.;
|
||||||
|
vendorHash = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
16
go.mod
Normal file
16
go.mod
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
module git.wntrmute.dev/kyle/arca
|
||||||
|
|
||||||
|
go 1.25.7
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/godbus/dbus/v5 v5.2.2
|
||||||
|
github.com/spf13/cobra v1.10.2
|
||||||
|
golang.org/x/term v0.41.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.9 // indirect
|
||||||
|
golang.org/x/sys v0.42.0 // indirect
|
||||||
|
)
|
||||||
19
go.sum
Normal file
19
go.sum
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
|
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
||||||
|
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||||
|
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||||
|
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||||
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||||
|
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
|
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
||||||
|
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
100
internal/config/config.go
Normal file
100
internal/config/config.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config holds the arca configuration.
|
||||||
|
type Config struct {
|
||||||
|
Devices map[string]DeviceConfig `yaml:"devices"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceConfig holds per-device settings.
|
||||||
|
type DeviceConfig struct {
|
||||||
|
UUID string `yaml:"uuid"`
|
||||||
|
Mountpoint string `yaml:"mountpoint,omitempty"`
|
||||||
|
Methods []string `yaml:"methods,omitempty"`
|
||||||
|
Keyfile string `yaml:"keyfile,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolvedDevice holds the effective settings for a device lookup.
|
||||||
|
type ResolvedDevice struct {
|
||||||
|
UUID string
|
||||||
|
Mountpoint string
|
||||||
|
Methods []string
|
||||||
|
Keyfile string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load reads the config file. Returns an empty config if the file doesn't exist.
|
||||||
|
func Load() *Config {
|
||||||
|
cfg := &Config{
|
||||||
|
Devices: make(map[string]DeviceConfig),
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(configPath())
|
||||||
|
if err != nil {
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
yaml.Unmarshal(data, cfg)
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveDevice looks up by alias name first, then checks if the argument
|
||||||
|
// is a device path matching a known alias (e.g. "/dev/sda1" matches "sda1").
|
||||||
|
// If nothing matches, returns defaults for a bare device path.
|
||||||
|
func (c *Config) ResolveDevice(nameOrPath string) ResolvedDevice {
|
||||||
|
// Direct alias match.
|
||||||
|
if dev, ok := c.Devices[nameOrPath]; ok {
|
||||||
|
return resolvedFrom(dev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match device path against aliases: "/dev/sda1" matches alias "sda1".
|
||||||
|
base := filepath.Base(nameOrPath)
|
||||||
|
if dev, ok := c.Devices[base]; ok {
|
||||||
|
return resolvedFrom(dev)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResolvedDevice{
|
||||||
|
Methods: []string{"passphrase"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolvedFrom(dev DeviceConfig) ResolvedDevice {
|
||||||
|
methods := dev.Methods
|
||||||
|
if len(methods) == 0 {
|
||||||
|
methods = []string{"passphrase"}
|
||||||
|
}
|
||||||
|
return ResolvedDevice{
|
||||||
|
UUID: dev.UUID,
|
||||||
|
Mountpoint: dev.Mountpoint,
|
||||||
|
Methods: methods,
|
||||||
|
Keyfile: dev.Keyfile,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AliasFor returns the config alias for a given UUID, or "" if none.
|
||||||
|
func (c *Config) AliasFor(uuid string) string {
|
||||||
|
for name, dev := range c.Devices {
|
||||||
|
if dev.UUID == uuid {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path returns the config file path, respecting XDG_CONFIG_HOME.
|
||||||
|
func Path() string {
|
||||||
|
return configPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func configPath() string {
|
||||||
|
if dir := os.Getenv("XDG_CONFIG_HOME"); dir != "" {
|
||||||
|
return filepath.Join(dir, "arca", "config.yaml")
|
||||||
|
}
|
||||||
|
home, _ := os.UserHomeDir()
|
||||||
|
return filepath.Join(home, ".config", "arca", "config.yaml")
|
||||||
|
}
|
||||||
133
internal/cryptsetup/cryptsetup.go
Normal file
133
internal/cryptsetup/cryptsetup.go
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package cryptsetup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Open opens a LUKS device using cryptsetup with token-based unlock.
|
||||||
|
func Open(devicePath, mapperName string) error {
|
||||||
|
args := withTokenPluginEnv([]string{"cryptsetup", "open", devicePath, mapperName, "--token-only"})
|
||||||
|
args = withPrivilege(args)
|
||||||
|
|
||||||
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("cryptsetup open --token-only: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes a LUKS mapping.
|
||||||
|
func Close(mapperName string) error {
|
||||||
|
args := []string{"cryptsetup", "close", mapperName}
|
||||||
|
args = withPrivilege(args)
|
||||||
|
|
||||||
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("cryptsetup close: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount mounts a device at the given mountpoint using privileged mount.
|
||||||
|
// If mountpoint is empty, mounts under /mnt/<basename>.
|
||||||
|
func Mount(devicePath, mountpoint string) (string, error) {
|
||||||
|
if mountpoint == "" {
|
||||||
|
mountpoint = "/mnt/" + filepath.Base(devicePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdirArgs := withPrivilege([]string{"mkdir", "-p", mountpoint})
|
||||||
|
if err := exec.Command(mkdirArgs[0], mkdirArgs[1:]...).Run(); err != nil {
|
||||||
|
return "", fmt.Errorf("creating mountpoint: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := withPrivilege([]string{"mount", devicePath, mountpoint})
|
||||||
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return "", fmt.Errorf("mount %s: %w", devicePath, err)
|
||||||
|
}
|
||||||
|
return mountpoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmount unmounts the given mountpoint using privileged umount.
|
||||||
|
func Unmount(mountpoint string) error {
|
||||||
|
args := withPrivilege([]string{"umount", mountpoint})
|
||||||
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("umount %s: %w", mountpoint, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapperName returns "arca-<dev>" from a device path, e.g. "/dev/sda1" -> "arca-sda1".
|
||||||
|
func MapperName(devicePath string) string {
|
||||||
|
return "arca-" + filepath.Base(devicePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// withTokenPluginEnv wraps the command with `env LD_LIBRARY_PATH=...` if
|
||||||
|
// the systemd cryptsetup token plugins are found.
|
||||||
|
//
|
||||||
|
// On NixOS, cryptsetup uses dlopen to load token plugins, but glibc only
|
||||||
|
// searches the binary's baked-in RUNPATH — which doesn't include the
|
||||||
|
// systemd plugin directory. LD_LIBRARY_PATH is the only reliable way to
|
||||||
|
// make dlopen find them.
|
||||||
|
//
|
||||||
|
// We inject it via `env` rather than cmd.Env so it survives privilege
|
||||||
|
// escalation through doas/sudo.
|
||||||
|
func withTokenPluginEnv(args []string) []string {
|
||||||
|
pluginDir := findTokenPluginDir()
|
||||||
|
if pluginDir == "" {
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
return append([]string{"env", "LD_LIBRARY_PATH=" + pluginDir}, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// findTokenPluginDir locates the directory containing libcryptsetup-token-*.so.
|
||||||
|
// It checks the NixOS system profile first, then falls back to resolving
|
||||||
|
// from the systemd-cryptenroll binary.
|
||||||
|
func findTokenPluginDir() string {
|
||||||
|
// NixOS stable symlink — survives rebuilds.
|
||||||
|
const nixSystemPath = "/run/current-system/sw/lib/cryptsetup"
|
||||||
|
if hasTokenPlugins(nixSystemPath) {
|
||||||
|
return nixSystemPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: derive from systemd-cryptenroll location.
|
||||||
|
if bin, err := exec.LookPath("systemd-cryptenroll"); err == nil {
|
||||||
|
if resolved, err := filepath.EvalSymlinks(bin); err == nil {
|
||||||
|
dir := filepath.Join(filepath.Dir(filepath.Dir(resolved)), "lib", "cryptsetup")
|
||||||
|
if hasTokenPlugins(dir) {
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasTokenPlugins(dir string) bool {
|
||||||
|
matches, _ := filepath.Glob(filepath.Join(dir, "libcryptsetup-token-*.so"))
|
||||||
|
return len(matches) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func withPrivilege(args []string) []string {
|
||||||
|
if _, err := exec.LookPath("doas"); err == nil {
|
||||||
|
return append([]string{"doas"}, args...)
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("sudo"); err == nil {
|
||||||
|
return append([]string{"sudo"}, args...)
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
177
internal/udisks/client.go
Normal file
177
internal/udisks/client.go
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
package udisks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client communicates with udisks2 over D-Bus.
|
||||||
|
type Client struct {
|
||||||
|
conn *dbus.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient connects to the system D-Bus and returns a udisks2 client.
|
||||||
|
func NewClient() (*Client, error) {
|
||||||
|
conn, err := dbus.SystemBus()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("connecting to system bus: %w", err)
|
||||||
|
}
|
||||||
|
return &Client{conn: conn}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the D-Bus connection.
|
||||||
|
func (c *Client) Close() {
|
||||||
|
c.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindDevice finds a block device by UUID (if non-empty) or device path.
|
||||||
|
func (c *Client) FindDevice(uuid, pathOrName string) (*BlockDevice, error) {
|
||||||
|
devices, err := c.listBlockDevices()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if uuid != "" {
|
||||||
|
for i := range devices {
|
||||||
|
if devices[i].UUID == uuid {
|
||||||
|
return &devices[i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no device with UUID %s", uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range devices {
|
||||||
|
if devices[i].DevicePath == pathOrName {
|
||||||
|
return &devices[i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("device %s not found", pathOrName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEncryptedDevices returns all block devices with the Encrypted interface.
|
||||||
|
func (c *Client) ListEncryptedDevices() ([]BlockDevice, error) {
|
||||||
|
devices, err := c.listBlockDevices()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var encrypted []BlockDevice
|
||||||
|
for _, d := range devices {
|
||||||
|
if d.HasEncrypted {
|
||||||
|
encrypted = append(encrypted, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return encrypted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleartextDevice finds the unlocked cleartext device for a LUKS device.
|
||||||
|
func (c *Client) CleartextDevice(dev *BlockDevice) (*BlockDevice, error) {
|
||||||
|
devices, err := c.listBlockDevices()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range devices {
|
||||||
|
if devices[i].CryptoBackingDevice == dev.ObjectPath {
|
||||||
|
return &devices[i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no cleartext device for %s (is it unlocked?)", dev.DevicePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MountPoint returns the current mount point of a device, or "" if not mounted.
|
||||||
|
func (c *Client) MountPoint(dev *BlockDevice) (string, error) {
|
||||||
|
obj := c.conn.Object(busName, dev.ObjectPath)
|
||||||
|
variant, err := obj.GetProperty(ifaceFilesystem + ".MountPoints")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
mountPoints, ok := variant.Value().([][]byte)
|
||||||
|
if !ok || len(mountPoints) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(bytes.TrimRight(mountPoints[0], "\x00")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceAtPath returns the block device at the given D-Bus object path.
|
||||||
|
func (c *Client) DeviceAtPath(path dbus.ObjectPath) (*BlockDevice, error) {
|
||||||
|
devices, err := c.listBlockDevices()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := range devices {
|
||||||
|
if devices[i].ObjectPath == path {
|
||||||
|
return &devices[i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("device at %s not found", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RootBackingDevices returns the object paths of LUKS devices that back
|
||||||
|
// the root filesystem. Used to exclude them from init config generation.
|
||||||
|
func (c *Client) RootBackingDevices() ([]dbus.ObjectPath, error) {
|
||||||
|
devices, err := c.listBlockDevices()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var backing []dbus.ObjectPath
|
||||||
|
for i := range devices {
|
||||||
|
if !devices[i].HasFilesystem {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mp, err := c.MountPoint(&devices[i])
|
||||||
|
if err != nil || mp != "/" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if devices[i].CryptoBackingDevice != "" && devices[i].CryptoBackingDevice != "/" {
|
||||||
|
backing = append(backing, devices[i].CryptoBackingDevice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return backing, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) listBlockDevices() ([]BlockDevice, error) {
|
||||||
|
obj := c.conn.Object(busName, dbus.ObjectPath(objectPrefix))
|
||||||
|
var managed map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
||||||
|
err := obj.Call(ifaceObjectManager+".GetManagedObjects", 0).Store(&managed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("listing devices: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var devices []BlockDevice
|
||||||
|
for path, ifaces := range managed {
|
||||||
|
blockProps, ok := ifaces[ifaceBlock]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dev := BlockDevice{ObjectPath: path}
|
||||||
|
|
||||||
|
if v, ok := blockProps["Device"]; ok {
|
||||||
|
if bs, ok := v.Value().([]byte); ok {
|
||||||
|
dev.DevicePath = string(bytes.TrimRight(bs, "\x00"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := blockProps["IdUUID"]; ok {
|
||||||
|
if s, ok := v.Value().(string); ok {
|
||||||
|
dev.UUID = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := blockProps["CryptoBackingDevice"]; ok {
|
||||||
|
if p, ok := v.Value().(dbus.ObjectPath); ok {
|
||||||
|
dev.CryptoBackingDevice = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, dev.HasEncrypted = ifaces[ifaceEncrypted]
|
||||||
|
_, dev.HasFilesystem = ifaces[ifaceFilesystem]
|
||||||
|
|
||||||
|
devices = append(devices, dev)
|
||||||
|
}
|
||||||
|
return devices, nil
|
||||||
|
}
|
||||||
43
internal/udisks/encrypt.go
Normal file
43
internal/udisks/encrypt.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package udisks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unlock unlocks a LUKS device with a passphrase and returns the cleartext device.
|
||||||
|
func (c *Client) Unlock(dev *BlockDevice, passphrase string) (*BlockDevice, error) {
|
||||||
|
obj := c.conn.Object(busName, dev.ObjectPath)
|
||||||
|
|
||||||
|
var cleartextPath dbus.ObjectPath
|
||||||
|
err := obj.Call(ifaceEncrypted+".Unlock", 0, passphrase, noOptions()).Store(&cleartextPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unlocking %s: %w", dev.DevicePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.DeviceAtPath(cleartextPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnlockWithKeyfile unlocks a LUKS device with keyfile contents.
|
||||||
|
func (c *Client) UnlockWithKeyfile(dev *BlockDevice, keyContents []byte) (*BlockDevice, error) {
|
||||||
|
obj := c.conn.Object(busName, dev.ObjectPath)
|
||||||
|
|
||||||
|
options := map[string]dbus.Variant{
|
||||||
|
"keyfile_contents": dbus.MakeVariant(keyContents),
|
||||||
|
}
|
||||||
|
|
||||||
|
var cleartextPath dbus.ObjectPath
|
||||||
|
err := obj.Call(ifaceEncrypted+".Unlock", 0, "", options).Store(&cleartextPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unlocking %s with keyfile: %w", dev.DevicePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.DeviceAtPath(cleartextPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock locks a LUKS device.
|
||||||
|
func (c *Client) Lock(dev *BlockDevice) error {
|
||||||
|
obj := c.conn.Object(busName, dev.ObjectPath)
|
||||||
|
return obj.Call(ifaceEncrypted+".Lock", 0, noOptions()).Err
|
||||||
|
}
|
||||||
23
internal/udisks/mount.go
Normal file
23
internal/udisks/mount.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package udisks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mount mounts a filesystem and returns the mount point chosen by udisks2.
|
||||||
|
func (c *Client) Mount(dev *BlockDevice) (string, error) {
|
||||||
|
obj := c.conn.Object(busName, dev.ObjectPath)
|
||||||
|
|
||||||
|
var mountpoint string
|
||||||
|
err := obj.Call(ifaceFilesystem+".Mount", 0, noOptions()).Store(&mountpoint)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("mounting %s: %w", dev.DevicePath, err)
|
||||||
|
}
|
||||||
|
return mountpoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmount unmounts a filesystem.
|
||||||
|
func (c *Client) Unmount(dev *BlockDevice) error {
|
||||||
|
obj := c.conn.Object(busName, dev.ObjectPath)
|
||||||
|
return obj.Call(ifaceFilesystem+".Unmount", 0, noOptions()).Err
|
||||||
|
}
|
||||||
27
internal/udisks/types.go
Normal file
27
internal/udisks/types.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package udisks
|
||||||
|
|
||||||
|
import "github.com/godbus/dbus/v5"
|
||||||
|
|
||||||
|
const (
|
||||||
|
busName = "org.freedesktop.UDisks2"
|
||||||
|
objectPrefix = "/org/freedesktop/UDisks2"
|
||||||
|
|
||||||
|
ifaceObjectManager = "org.freedesktop.DBus.ObjectManager"
|
||||||
|
ifaceBlock = "org.freedesktop.UDisks2.Block"
|
||||||
|
ifaceEncrypted = "org.freedesktop.UDisks2.Encrypted"
|
||||||
|
ifaceFilesystem = "org.freedesktop.UDisks2.Filesystem"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BlockDevice represents a udisks2 block device.
|
||||||
|
type BlockDevice struct {
|
||||||
|
ObjectPath dbus.ObjectPath
|
||||||
|
DevicePath string // e.g., "/dev/sda1"
|
||||||
|
UUID string
|
||||||
|
HasEncrypted bool
|
||||||
|
HasFilesystem bool
|
||||||
|
CryptoBackingDevice dbus.ObjectPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func noOptions() map[string]dbus.Variant {
|
||||||
|
return make(map[string]dbus.Variant)
|
||||||
|
}
|
||||||
111
internal/unlock/unlock.go
Normal file
111
internal/unlock/unlock.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
package unlock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/arca/internal/cryptsetup"
|
||||||
|
"git.wntrmute.dev/kyle/arca/internal/udisks"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Result holds the outcome of a successful unlock.
|
||||||
|
type Result struct {
|
||||||
|
Device *udisks.BlockDevice
|
||||||
|
Privileged bool // true if unlock required root (cryptsetup path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options configures the unlock behavior.
|
||||||
|
type Options struct {
|
||||||
|
ReadPassphrase func() (string, error)
|
||||||
|
KeyfilePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlocker tries unlock methods in sequence.
|
||||||
|
type Unlocker struct {
|
||||||
|
client *udisks.Client
|
||||||
|
opts Options
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Unlocker.
|
||||||
|
func New(client *udisks.Client, opts Options) *Unlocker {
|
||||||
|
return &Unlocker{client: client, opts: opts}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock tries each method in order and returns the result on first success.
|
||||||
|
func (u *Unlocker) Unlock(dev *udisks.BlockDevice, methods []string) (*Result, error) {
|
||||||
|
var errs []error
|
||||||
|
for _, method := range methods {
|
||||||
|
res, err := u.tryMethod(dev, method)
|
||||||
|
if err == nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
errs = append(errs, fmt.Errorf("%s: %w", method, err))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("all unlock methods failed:\n%w", errors.Join(errs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unlocker) tryMethod(dev *udisks.BlockDevice, method string) (*Result, error) {
|
||||||
|
switch method {
|
||||||
|
case "passphrase":
|
||||||
|
ct, err := u.unlockPassphrase(dev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Result{Device: ct, Privileged: false}, nil
|
||||||
|
case "keyfile":
|
||||||
|
ct, err := u.unlockKeyfile(dev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Result{Device: ct, Privileged: false}, nil
|
||||||
|
case "fido2", "tpm2":
|
||||||
|
ct, err := u.unlockCryptsetup(dev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Result{Device: ct, Privileged: true}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown unlock method: %s", method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unlocker) unlockPassphrase(dev *udisks.BlockDevice) (*udisks.BlockDevice, error) {
|
||||||
|
if u.opts.ReadPassphrase == nil {
|
||||||
|
return nil, fmt.Errorf("no passphrase reader configured")
|
||||||
|
}
|
||||||
|
pass, err := u.opts.ReadPassphrase()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return u.client.Unlock(dev, pass)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unlocker) unlockKeyfile(dev *udisks.BlockDevice) (*udisks.BlockDevice, error) {
|
||||||
|
if u.opts.KeyfilePath == "" {
|
||||||
|
return nil, fmt.Errorf("no keyfile configured")
|
||||||
|
}
|
||||||
|
contents, err := os.ReadFile(u.opts.KeyfilePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reading keyfile: %w", err)
|
||||||
|
}
|
||||||
|
return u.client.UnlockWithKeyfile(dev, contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unlocker) unlockCryptsetup(dev *udisks.BlockDevice) (*udisks.BlockDevice, error) {
|
||||||
|
name := cryptsetup.MapperName(dev.DevicePath)
|
||||||
|
if err := cryptsetup.Open(dev.DevicePath, name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for udisks2 to pick up the new dm device.
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
ct, err := u.client.CleartextDevice(dev)
|
||||||
|
if err == nil {
|
||||||
|
return ct, nil
|
||||||
|
}
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("timed out waiting for udisks2 to discover cleartext device")
|
||||||
|
}
|
||||||
7
main.go
Normal file
7
main.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "git.wntrmute.dev/kyle/arca/cmd"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd.Execute()
|
||||||
|
}
|
||||||
11
vendor/github.com/godbus/dbus/v5/.cirrus.yml
generated
vendored
Normal file
11
vendor/github.com/godbus/dbus/v5/.cirrus.yml
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# See https://cirrus-ci.org/guide/FreeBSD/
|
||||||
|
freebsd_instance:
|
||||||
|
image_family: freebsd-14-3
|
||||||
|
|
||||||
|
task:
|
||||||
|
name: Test on FreeBSD
|
||||||
|
install_script: pkg install -y go125 dbus
|
||||||
|
test_script: |
|
||||||
|
/usr/local/etc/rc.d/dbus onestart && \
|
||||||
|
eval `dbus-launch --sh-syntax` && \
|
||||||
|
go125 test -v ./...
|
||||||
13
vendor/github.com/godbus/dbus/v5/.golangci.yml
generated
vendored
Normal file
13
vendor/github.com/godbus/dbus/v5/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
version: "2"
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
exclusions:
|
||||||
|
presets:
|
||||||
|
- std-error-handling
|
||||||
|
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- gofumpt
|
||||||
50
vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md
generated
vendored
Normal file
50
vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# How to Contribute
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
- Fork the repository on GitHub
|
||||||
|
- Read the [README](README.markdown) for build and test instructions
|
||||||
|
- Play with the project, submit bugs, submit patches!
|
||||||
|
|
||||||
|
## Contribution Flow
|
||||||
|
|
||||||
|
This is a rough outline of what a contributor's workflow looks like:
|
||||||
|
|
||||||
|
- Create a topic branch from where you want to base your work (usually master).
|
||||||
|
- Make commits of logical units.
|
||||||
|
- Make sure your commit messages are in the proper format (see below).
|
||||||
|
- Push your changes to a topic branch in your fork of the repository.
|
||||||
|
- Make sure the tests pass, and add any new tests as appropriate.
|
||||||
|
- Submit a pull request to the original repository.
|
||||||
|
|
||||||
|
Thanks for your contributions!
|
||||||
|
|
||||||
|
### Format of the Commit Message
|
||||||
|
|
||||||
|
We follow a rough convention for commit messages that is designed to answer two
|
||||||
|
questions: what changed and why. The subject line should feature the what and
|
||||||
|
the body of the commit should describe the why.
|
||||||
|
|
||||||
|
```
|
||||||
|
scripts: add the test-cluster command
|
||||||
|
|
||||||
|
this uses tmux to setup a test cluster that you can easily kill and
|
||||||
|
start for debugging.
|
||||||
|
|
||||||
|
Fixes #38
|
||||||
|
```
|
||||||
|
|
||||||
|
The format can be described more formally as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
<subsystem>: <what changed>
|
||||||
|
<BLANK LINE>
|
||||||
|
<why this change was made>
|
||||||
|
<BLANK LINE>
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
The first line is the subject and should be no longer than 70 characters, the
|
||||||
|
second line is always blank, and other lines should be wrapped at 80 characters.
|
||||||
|
This allows the message to be easier to read on GitHub as well as in various
|
||||||
|
git tools.
|
||||||
25
vendor/github.com/godbus/dbus/v5/LICENSE
generated
vendored
Normal file
25
vendor/github.com/godbus/dbus/v5/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
Copyright (c) 2013, Georg Reinke (<guelfey at gmail dot com>), Google
|
||||||
|
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.
|
||||||
3
vendor/github.com/godbus/dbus/v5/MAINTAINERS
generated
vendored
Normal file
3
vendor/github.com/godbus/dbus/v5/MAINTAINERS
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Brandon Philips <brandon@ifup.org> (@philips)
|
||||||
|
Brian Waldon <brian@waldon.cc> (@bcwaldon)
|
||||||
|
John Southworth <jsouthwo@brocade.com> (@jsouthworth)
|
||||||
47
vendor/github.com/godbus/dbus/v5/README.md
generated
vendored
Normal file
47
vendor/github.com/godbus/dbus/v5/README.md
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|

|
||||||
|
|
||||||
|
dbus
|
||||||
|
----
|
||||||
|
|
||||||
|
dbus is a simple library that implements native Go client bindings for the
|
||||||
|
D-Bus message bus system.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Complete native implementation of the D-Bus message protocol
|
||||||
|
* Go-like API (channels for signals / asynchronous method calls, Goroutine-safe connections)
|
||||||
|
* Subpackages that help with the introspection / property interfaces
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
This packages requires Go 1.20 or later. It can be installed by running the command below:
|
||||||
|
|
||||||
|
```
|
||||||
|
go get github.com/godbus/dbus/v5
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
The complete package documentation and some simple examples are available at
|
||||||
|
[pkg.go.dev](https://pkg.go.dev/github.com/godbus/dbus/v5). Also, the
|
||||||
|
[_examples](https://github.com/godbus/dbus/tree/master/_examples) directory
|
||||||
|
gives a short overview over the basic usage.
|
||||||
|
|
||||||
|
#### Projects using godbus
|
||||||
|
- [fyne](https://github.com/fyne-io/fyne) a cross platform GUI in Go inspired by Material Design.
|
||||||
|
- [fynedesk](https://github.com/fyne-io/fynedesk) a full desktop environment for Linux/Unix using Fyne.
|
||||||
|
- [go-bluetooth](https://github.com/muka/go-bluetooth) provides a bluetooth client over bluez dbus API.
|
||||||
|
- [iwd](https://github.com/shibumi/iwd) go bindings for the internet wireless daemon "iwd".
|
||||||
|
- [notify](https://github.com/esiqveland/notify) provides desktop notifications over dbus into a library.
|
||||||
|
- [playerbm](https://github.com/altdesktop/playerbm) a bookmark utility for media players.
|
||||||
|
- [rpic](https://github.com/stephenhu/rpic) lightweight web app and RESTful API for managing a Raspberry Pi
|
||||||
|
|
||||||
|
Please note that the API is considered unstable for now and may change without
|
||||||
|
further notice.
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
go.dbus is available under the Simplified BSD License; see LICENSE for the full
|
||||||
|
text.
|
||||||
|
|
||||||
|
Nearly all of the credit for this library goes to github.com/guelfey/go.dbus.
|
||||||
13
vendor/github.com/godbus/dbus/v5/SECURITY.md
generated
vendored
Normal file
13
vendor/github.com/godbus/dbus/v5/SECURITY.md
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
Security updates are applied only to the latest release.
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released.
|
||||||
|
|
||||||
|
Please disclose it at [security advisory](https://github.com/godbus/dbus/security/advisories/new).
|
||||||
|
|
||||||
|
This project is maintained by a team of volunteers on a reasonable-effort basis. As such, vulnerabilities will be disclosed in a best effort base.
|
||||||
257
vendor/github.com/godbus/dbus/v5/auth.go
generated
vendored
Normal file
257
vendor/github.com/godbus/dbus/v5/auth.go
generated
vendored
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthStatus represents the Status of an authentication mechanism.
|
||||||
|
type AuthStatus byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AuthOk signals that authentication is finished; the next command
|
||||||
|
// from the server should be an OK.
|
||||||
|
AuthOk AuthStatus = iota
|
||||||
|
|
||||||
|
// AuthContinue signals that additional data is needed; the next command
|
||||||
|
// from the server should be a DATA.
|
||||||
|
AuthContinue
|
||||||
|
|
||||||
|
// AuthError signals an error; the server sent invalid data or some
|
||||||
|
// other unexpected thing happened and the current authentication
|
||||||
|
// process should be aborted.
|
||||||
|
AuthError
|
||||||
|
)
|
||||||
|
|
||||||
|
type authState byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
waitingForData authState = iota
|
||||||
|
waitingForOk
|
||||||
|
waitingForReject
|
||||||
|
)
|
||||||
|
|
||||||
|
// Auth defines the behaviour of an authentication mechanism.
|
||||||
|
type Auth interface {
|
||||||
|
// Return the name of the mechanism, the argument to the first AUTH command
|
||||||
|
// and the next status.
|
||||||
|
FirstData() (name, resp []byte, status AuthStatus)
|
||||||
|
|
||||||
|
// Process the given DATA command, and return the argument to the DATA
|
||||||
|
// command and the next status. If len(resp) == 0, no DATA command is sent.
|
||||||
|
HandleData(data []byte) (resp []byte, status AuthStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth authenticates the connection, trying the given list of authentication
|
||||||
|
// mechanisms (in that order). If nil is passed, the EXTERNAL and
|
||||||
|
// DBUS_COOKIE_SHA1 mechanisms are tried for the current user. For private
|
||||||
|
// connections, this method must be called before sending any messages to the
|
||||||
|
// bus. Auth must not be called on shared connections.
|
||||||
|
func (conn *Conn) Auth(methods []Auth) error {
|
||||||
|
if methods == nil {
|
||||||
|
uid := strconv.Itoa(os.Geteuid())
|
||||||
|
methods = getDefaultAuthMethods(uid)
|
||||||
|
}
|
||||||
|
in := bufio.NewReader(conn.transport)
|
||||||
|
err := conn.SendNullByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = authWriteLine(conn.transport, []byte("AUTH"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s, err := authReadLine(in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(s) < 2 || !bytes.Equal(s[0], []byte("REJECTED")) {
|
||||||
|
return errors.New("dbus: authentication protocol error")
|
||||||
|
}
|
||||||
|
s = s[1:]
|
||||||
|
for _, v := range s {
|
||||||
|
for _, m := range methods {
|
||||||
|
if name, _, status := m.FirstData(); bytes.Equal(v, name) {
|
||||||
|
var ok bool
|
||||||
|
err = authWriteLine(conn.transport, []byte("AUTH"), v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch status {
|
||||||
|
case AuthOk:
|
||||||
|
ok, err = conn.tryAuth(m, waitingForOk, in)
|
||||||
|
case AuthContinue:
|
||||||
|
ok, err = conn.tryAuth(m, waitingForData, in)
|
||||||
|
default:
|
||||||
|
panic("dbus: invalid authentication status")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
if conn.transport.SupportsUnixFDs() {
|
||||||
|
err = authWriteLine(conn, []byte("NEGOTIATE_UNIX_FD"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
line, err := authReadLine(in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case bytes.Equal(line[0], []byte("AGREE_UNIX_FD")):
|
||||||
|
conn.EnableUnixFDs()
|
||||||
|
conn.unixFD = true
|
||||||
|
case bytes.Equal(line[0], []byte("ERROR")):
|
||||||
|
default:
|
||||||
|
return errors.New("dbus: authentication protocol error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = authWriteLine(conn.transport, []byte("BEGIN"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go conn.inWorker()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("dbus: authentication failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryAuth tries to authenticate with m as the mechanism, using state as the
|
||||||
|
// initial authState and in for reading input. It returns (true, nil) on
|
||||||
|
// success, (false, nil) on a REJECTED and (false, someErr) if some other
|
||||||
|
// error occurred.
|
||||||
|
func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (bool, error) {
|
||||||
|
for {
|
||||||
|
s, err := authReadLine(in)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case state == waitingForData && string(s[0]) == "DATA":
|
||||||
|
if len(s) != 2 {
|
||||||
|
err = authWriteLine(conn.transport, []byte("ERROR"))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data, status := m.HandleData(s[1])
|
||||||
|
switch status {
|
||||||
|
case AuthOk, AuthContinue:
|
||||||
|
if len(data) != 0 {
|
||||||
|
err = authWriteLine(conn.transport, []byte("DATA"), data)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if status == AuthOk {
|
||||||
|
state = waitingForOk
|
||||||
|
}
|
||||||
|
case AuthError:
|
||||||
|
err = authWriteLine(conn.transport, []byte("ERROR"))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case state == waitingForData && string(s[0]) == "REJECTED":
|
||||||
|
return false, nil
|
||||||
|
case state == waitingForData && string(s[0]) == "ERROR":
|
||||||
|
err = authWriteLine(conn.transport, []byte("CANCEL"))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
state = waitingForReject
|
||||||
|
case state == waitingForData && string(s[0]) == "OK":
|
||||||
|
if len(s) != 2 {
|
||||||
|
err = authWriteLine(conn.transport, []byte("CANCEL"))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
state = waitingForReject
|
||||||
|
} else {
|
||||||
|
conn.uuid = string(s[1])
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
case state == waitingForData:
|
||||||
|
err = authWriteLine(conn.transport, []byte("ERROR"))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
case state == waitingForOk && string(s[0]) == "OK":
|
||||||
|
if len(s) != 2 {
|
||||||
|
err = authWriteLine(conn.transport, []byte("CANCEL"))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
state = waitingForReject
|
||||||
|
} else {
|
||||||
|
conn.uuid = string(s[1])
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
case state == waitingForOk && string(s[0]) == "DATA":
|
||||||
|
err = authWriteLine(conn.transport, []byte("DATA"))
|
||||||
|
if err != nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
case state == waitingForOk && string(s[0]) == "REJECTED":
|
||||||
|
return false, nil
|
||||||
|
case state == waitingForOk && string(s[0]) == "ERROR":
|
||||||
|
err = authWriteLine(conn.transport, []byte("CANCEL"))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
state = waitingForReject
|
||||||
|
case state == waitingForOk:
|
||||||
|
err = authWriteLine(conn.transport, []byte("ERROR"))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
case state == waitingForReject && string(s[0]) == "REJECTED":
|
||||||
|
return false, nil
|
||||||
|
case state == waitingForReject:
|
||||||
|
return false, errors.New("dbus: authentication protocol error")
|
||||||
|
default:
|
||||||
|
panic("dbus: invalid auth state")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// authReadLine reads a line and separates it into its fields.
|
||||||
|
func authReadLine(in *bufio.Reader) ([][]byte, error) {
|
||||||
|
data, err := in.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data = bytes.TrimSuffix(data, []byte("\r\n"))
|
||||||
|
return bytes.Split(data, []byte{' '}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// authWriteLine writes the given line in the authentication protocol format
|
||||||
|
// (elements of data separated by a " " and terminated by "\r\n").
|
||||||
|
func authWriteLine(out io.Writer, data ...[]byte) error {
|
||||||
|
buf := make([]byte, 0)
|
||||||
|
for i, v := range data {
|
||||||
|
buf = append(buf, v...)
|
||||||
|
if i != len(data)-1 {
|
||||||
|
buf = append(buf, ' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = append(buf, '\r')
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
n, err := out.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != len(buf) {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
16
vendor/github.com/godbus/dbus/v5/auth_anonymous.go
generated
vendored
Normal file
16
vendor/github.com/godbus/dbus/v5/auth_anonymous.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
// AuthAnonymous returns an Auth that uses the ANONYMOUS mechanism.
|
||||||
|
func AuthAnonymous() Auth {
|
||||||
|
return &authAnonymous{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type authAnonymous struct{}
|
||||||
|
|
||||||
|
func (a *authAnonymous) FirstData() (name, resp []byte, status AuthStatus) {
|
||||||
|
return []byte("ANONYMOUS"), nil, AuthOk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authAnonymous) HandleData(data []byte) (resp []byte, status AuthStatus) {
|
||||||
|
return nil, AuthError
|
||||||
|
}
|
||||||
7
vendor/github.com/godbus/dbus/v5/auth_default_other.go
generated
vendored
Normal file
7
vendor/github.com/godbus/dbus/v5/auth_default_other.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package dbus
|
||||||
|
|
||||||
|
func getDefaultAuthMethods(user string) []Auth {
|
||||||
|
return []Auth{AuthExternal(user)}
|
||||||
|
}
|
||||||
5
vendor/github.com/godbus/dbus/v5/auth_default_windows.go
generated
vendored
Normal file
5
vendor/github.com/godbus/dbus/v5/auth_default_windows.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
func getDefaultAuthMethods(user string) []Auth {
|
||||||
|
return []Auth{AuthCookieSha1(user, getHomeDir())}
|
||||||
|
}
|
||||||
26
vendor/github.com/godbus/dbus/v5/auth_external.go
generated
vendored
Normal file
26
vendor/github.com/godbus/dbus/v5/auth_external.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthExternal returns an Auth that authenticates as the given user with the
|
||||||
|
// EXTERNAL mechanism.
|
||||||
|
func AuthExternal(user string) Auth {
|
||||||
|
return authExternal{user}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthExternal implements the EXTERNAL authentication mechanism.
|
||||||
|
type authExternal struct {
|
||||||
|
user string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a authExternal) FirstData() ([]byte, []byte, AuthStatus) {
|
||||||
|
b := make([]byte, 2*len(a.user))
|
||||||
|
hex.Encode(b, []byte(a.user))
|
||||||
|
return []byte("EXTERNAL"), b, AuthOk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a authExternal) HandleData(b []byte) ([]byte, AuthStatus) {
|
||||||
|
return nil, AuthError
|
||||||
|
}
|
||||||
109
vendor/github.com/godbus/dbus/v5/auth_sha1_windows.go
generated
vendored
Normal file
109
vendor/github.com/godbus/dbus/v5/auth_sha1_windows.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthCookieSha1 returns an Auth that authenticates as the given user with the
|
||||||
|
// DBUS_COOKIE_SHA1 mechanism. The home parameter should specify the home
|
||||||
|
// directory of the user.
|
||||||
|
func AuthCookieSha1(user, home string) Auth {
|
||||||
|
return authCookieSha1{user, home}
|
||||||
|
}
|
||||||
|
|
||||||
|
type authCookieSha1 struct {
|
||||||
|
user, home string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a authCookieSha1) FirstData() ([]byte, []byte, AuthStatus) {
|
||||||
|
b := make([]byte, 2*len(a.user))
|
||||||
|
hex.Encode(b, []byte(a.user))
|
||||||
|
return []byte("DBUS_COOKIE_SHA1"), b, AuthContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a authCookieSha1) HandleData(data []byte) ([]byte, AuthStatus) {
|
||||||
|
challenge := make([]byte, len(data)/2)
|
||||||
|
_, err := hex.Decode(challenge, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, AuthError
|
||||||
|
}
|
||||||
|
b := bytes.Split(challenge, []byte{' '})
|
||||||
|
if len(b) != 3 {
|
||||||
|
return nil, AuthError
|
||||||
|
}
|
||||||
|
context := b[0]
|
||||||
|
id := b[1]
|
||||||
|
svchallenge := b[2]
|
||||||
|
cookie := a.getCookie(context, id)
|
||||||
|
if cookie == nil {
|
||||||
|
return nil, AuthError
|
||||||
|
}
|
||||||
|
clchallenge := a.generateChallenge()
|
||||||
|
if clchallenge == nil {
|
||||||
|
return nil, AuthError
|
||||||
|
}
|
||||||
|
hash := sha1.New()
|
||||||
|
hash.Write(bytes.Join([][]byte{svchallenge, clchallenge, cookie}, []byte{':'}))
|
||||||
|
hexhash := make([]byte, 2*hash.Size())
|
||||||
|
hex.Encode(hexhash, hash.Sum(nil))
|
||||||
|
data = append(clchallenge, ' ')
|
||||||
|
data = append(data, hexhash...)
|
||||||
|
resp := make([]byte, 2*len(data))
|
||||||
|
hex.Encode(resp, data)
|
||||||
|
return resp, AuthOk
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCookie searches for the cookie identified by id in context and returns
|
||||||
|
// the cookie content or nil. (Since HandleData can't return a specific error,
|
||||||
|
// but only whether an error occurred, this function also doesn't bother to
|
||||||
|
// return an error.)
|
||||||
|
func (a authCookieSha1) getCookie(context, id []byte) []byte {
|
||||||
|
file, err := os.Open(a.home + "/.dbus-keyrings/" + string(context))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
rd := bufio.NewReader(file)
|
||||||
|
for {
|
||||||
|
line, err := rd.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
line = line[:len(line)-1]
|
||||||
|
b := bytes.Split(line, []byte{' '})
|
||||||
|
if len(b) != 3 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if bytes.Equal(b[0], id) {
|
||||||
|
return b[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateChallenge returns a random, hex-encoded challenge, or nil on error
|
||||||
|
// (see above).
|
||||||
|
func (a authCookieSha1) generateChallenge() []byte {
|
||||||
|
b := make([]byte, 16)
|
||||||
|
n, err := rand.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if n != 16 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
enc := make([]byte, 32)
|
||||||
|
hex.Encode(enc, b)
|
||||||
|
return enc
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHomeDir() string {
|
||||||
|
if dir, err := os.UserHomeDir(); err == nil {
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
66
vendor/github.com/godbus/dbus/v5/call.go
generated
vendored
Normal file
66
vendor/github.com/godbus/dbus/v5/call.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Call represents a pending or completed method call.
|
||||||
|
type Call struct {
|
||||||
|
Destination string
|
||||||
|
Path ObjectPath
|
||||||
|
Method string
|
||||||
|
Args []any
|
||||||
|
|
||||||
|
// Strobes when the call is complete.
|
||||||
|
Done chan *Call
|
||||||
|
|
||||||
|
// After completion, the error status. If this is non-nil, it may be an
|
||||||
|
// error message from the peer (with Error as its type) or some other error.
|
||||||
|
Err error
|
||||||
|
|
||||||
|
// Holds the response once the call is done.
|
||||||
|
Body []any
|
||||||
|
|
||||||
|
// ResponseSequence stores the sequence number of the DBus message containing
|
||||||
|
// the call response (or error). This can be compared to the sequence number
|
||||||
|
// of other call responses and signals on this connection to determine their
|
||||||
|
// relative ordering on the underlying DBus connection.
|
||||||
|
// For errors, ResponseSequence is populated only if the error came from a
|
||||||
|
// DBusMessage that was received or if there was an error receiving. In case of
|
||||||
|
// failure to make the call, ResponseSequence will be NoSequence.
|
||||||
|
ResponseSequence Sequence
|
||||||
|
|
||||||
|
// tracks context and canceler
|
||||||
|
ctx context.Context
|
||||||
|
ctxCanceler context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Call) Context() context.Context {
|
||||||
|
if c.ctx == nil {
|
||||||
|
return context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Call) ContextCancel() {
|
||||||
|
if c.ctxCanceler != nil {
|
||||||
|
c.ctxCanceler()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store stores the body of the reply into the provided pointers. It returns
|
||||||
|
// an error if the signatures of the body and retvalues don't match, or if
|
||||||
|
// the error status is not nil.
|
||||||
|
func (c *Call) Store(retvalues ...any) error {
|
||||||
|
if c.Err != nil {
|
||||||
|
return c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Store(c.Body, retvalues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Call) done() {
|
||||||
|
c.Done <- c
|
||||||
|
c.ContextCancel()
|
||||||
|
}
|
||||||
1009
vendor/github.com/godbus/dbus/v5/conn.go
generated
vendored
Normal file
1009
vendor/github.com/godbus/dbus/v5/conn.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
36
vendor/github.com/godbus/dbus/v5/conn_darwin.go
generated
vendored
Normal file
36
vendor/github.com/godbus/dbus/v5/conn_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultSystemBusAddress = "unix:path=/opt/local/var/run/dbus/system_bus_socket"
|
||||||
|
|
||||||
|
func getSessionBusPlatformAddress() (string, error) {
|
||||||
|
cmd := exec.Command("launchctl", "getenv", "DBUS_LAUNCHD_SESSION_BUS_SOCKET")
|
||||||
|
b, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) == 0 {
|
||||||
|
return "", errors.New("dbus: couldn't determine address of session bus")
|
||||||
|
}
|
||||||
|
|
||||||
|
return "unix:path=" + string(b[:len(b)-1]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSystemBusPlatformAddress() string {
|
||||||
|
address := os.Getenv("DBUS_LAUNCHD_SESSION_BUS_SOCKET")
|
||||||
|
if address != "" {
|
||||||
|
return fmt.Sprintf("unix:path=%s", address)
|
||||||
|
}
|
||||||
|
return defaultSystemBusAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryDiscoverDbusSessionBusAddress() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
83
vendor/github.com/godbus/dbus/v5/conn_other.go
generated
vendored
Normal file
83
vendor/github.com/godbus/dbus/v5/conn_other.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
//go:build !darwin
|
||||||
|
|
||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var execCommand = exec.Command
|
||||||
|
|
||||||
|
func getSessionBusPlatformAddress() (string, error) {
|
||||||
|
cmd := execCommand("dbus-launch")
|
||||||
|
b, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
i := bytes.IndexByte(b, '=')
|
||||||
|
j := bytes.IndexByte(b, '\n')
|
||||||
|
|
||||||
|
if i == -1 || j == -1 || i > j {
|
||||||
|
return "", errors.New("dbus: couldn't determine address of session bus")
|
||||||
|
}
|
||||||
|
|
||||||
|
env, addr := string(b[0:i]), string(b[i+1:j])
|
||||||
|
os.Setenv(env, addr)
|
||||||
|
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryDiscoverDbusSessionBusAddress tries to discover an existing dbus session
|
||||||
|
// and return the value of its DBUS_SESSION_BUS_ADDRESS.
|
||||||
|
// It tries different techniques employed by different operating systems,
|
||||||
|
// returning the first valid address it finds, or an empty string.
|
||||||
|
//
|
||||||
|
// - /run/user/<uid>/bus if this exists, it *is* the bus socket. present on
|
||||||
|
// Ubuntu 18.04
|
||||||
|
// - /run/user/<uid>/dbus-session: if this exists, it can be parsed for the bus
|
||||||
|
// address. present on Ubuntu 16.04
|
||||||
|
//
|
||||||
|
// See https://dbus.freedesktop.org/doc/dbus-launch.1.html
|
||||||
|
func tryDiscoverDbusSessionBusAddress() string {
|
||||||
|
if runtimeDirectory, err := getRuntimeDirectory(); err == nil {
|
||||||
|
|
||||||
|
if runUserBusFile := path.Join(runtimeDirectory, "bus"); fileExists(runUserBusFile) {
|
||||||
|
// if /run/user/<uid>/bus exists, that file itself
|
||||||
|
// *is* the unix socket, so return its path
|
||||||
|
return fmt.Sprintf("unix:path=%s", EscapeBusAddressValue(runUserBusFile))
|
||||||
|
}
|
||||||
|
if runUserSessionDbusFile := path.Join(runtimeDirectory, "dbus-session"); fileExists(runUserSessionDbusFile) {
|
||||||
|
// if /run/user/<uid>/dbus-session exists, it's a
|
||||||
|
// text file // containing the address of the socket, e.g.:
|
||||||
|
// DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-E1c73yNqrG
|
||||||
|
|
||||||
|
if f, err := os.ReadFile(runUserSessionDbusFile); err == nil {
|
||||||
|
if addr, ok := strings.CutPrefix(string(f), "DBUS_SESSION_BUS_ADDRESS="); ok {
|
||||||
|
return strings.TrimRight(addr, "\n\r")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRuntimeDirectory() (string, error) {
|
||||||
|
if currentUser, err := user.Current(); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("/run/user/%s", currentUser.Uid), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileExists(filename string) bool {
|
||||||
|
_, err := os.Stat(filename)
|
||||||
|
return !os.IsNotExist(err)
|
||||||
|
}
|
||||||
40
vendor/github.com/godbus/dbus/v5/conn_unix.go
generated
vendored
Normal file
40
vendor/github.com/godbus/dbus/v5/conn_unix.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
//go:build !windows && !solaris && !darwin
|
||||||
|
|
||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultSystemBusAddress = "unix:path=/var/run/dbus/system_bus_socket"
|
||||||
|
|
||||||
|
func getSystemBusPlatformAddress() string {
|
||||||
|
address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS")
|
||||||
|
if address != "" {
|
||||||
|
return address
|
||||||
|
}
|
||||||
|
return defaultSystemBusAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialUnix establishes a new private connection to the message bus specified by UnixConn.
|
||||||
|
func DialUnix(conn *net.UnixConn, opts ...ConnOption) (*Conn, error) {
|
||||||
|
tr := newUnixTransportFromConn(conn)
|
||||||
|
return newConn(tr, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConnectUnix(uconn *net.UnixConn, opts ...ConnOption) (*Conn, error) {
|
||||||
|
conn, err := DialUnix(uconn, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = conn.Auth(conn.auth); err != nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = conn.Hello(); err != nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
13
vendor/github.com/godbus/dbus/v5/conn_windows.go
generated
vendored
Normal file
13
vendor/github.com/godbus/dbus/v5/conn_windows.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
const defaultSystemBusAddress = "tcp:host=127.0.0.1,port=12434"
|
||||||
|
|
||||||
|
func getSystemBusPlatformAddress() string {
|
||||||
|
address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS")
|
||||||
|
if address != "" {
|
||||||
|
return address
|
||||||
|
}
|
||||||
|
return defaultSystemBusAddress
|
||||||
|
}
|
||||||
427
vendor/github.com/godbus/dbus/v5/dbus.go
generated
vendored
Normal file
427
vendor/github.com/godbus/dbus/v5/dbus.go
generated
vendored
Normal file
@@ -0,0 +1,427 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
byteType = reflect.TypeOf(byte(0))
|
||||||
|
boolType = reflect.TypeOf(false)
|
||||||
|
int16Type = reflect.TypeOf(int16(0))
|
||||||
|
uint16Type = reflect.TypeOf(uint16(0))
|
||||||
|
int32Type = reflect.TypeOf(int32(0))
|
||||||
|
uint32Type = reflect.TypeOf(uint32(0))
|
||||||
|
int64Type = reflect.TypeOf(int64(0))
|
||||||
|
uint64Type = reflect.TypeOf(uint64(0))
|
||||||
|
float64Type = reflect.TypeOf(float64(0))
|
||||||
|
stringType = reflect.TypeOf("")
|
||||||
|
signatureType = reflect.TypeOf(Signature{""})
|
||||||
|
objectPathType = reflect.TypeOf(ObjectPath(""))
|
||||||
|
variantType = reflect.TypeOf(Variant{Signature{""}, nil})
|
||||||
|
interfacesType = reflect.TypeOf([]any{})
|
||||||
|
interfaceType = reflect.TypeOf((*any)(nil)).Elem()
|
||||||
|
unixFDType = reflect.TypeOf(UnixFD(0))
|
||||||
|
unixFDIndexType = reflect.TypeOf(UnixFDIndex(0))
|
||||||
|
errType = reflect.TypeOf((*error)(nil)).Elem()
|
||||||
|
)
|
||||||
|
|
||||||
|
// An InvalidTypeError signals that a value which cannot be represented in the
|
||||||
|
// D-Bus wire format was passed to a function.
|
||||||
|
type InvalidTypeError struct {
|
||||||
|
Type reflect.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e InvalidTypeError) Error() string {
|
||||||
|
return "dbus: invalid type " + e.Type.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store copies the values contained in src to dest, which must be a slice of
|
||||||
|
// pointers. It converts slices of interfaces from src to corresponding structs
|
||||||
|
// in dest. An error is returned if the lengths of src and dest or the types of
|
||||||
|
// their elements don't match.
|
||||||
|
func Store(src []any, dest ...any) error {
|
||||||
|
if len(src) != len(dest) {
|
||||||
|
return errors.New("dbus.Store: length mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range src {
|
||||||
|
if err := storeInterfaces(src[i], dest[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeInterfaces(src, dest any) error {
|
||||||
|
return store(reflect.ValueOf(dest), reflect.ValueOf(src))
|
||||||
|
}
|
||||||
|
|
||||||
|
func store(dest, src reflect.Value) error {
|
||||||
|
if dest.Kind() == reflect.Ptr {
|
||||||
|
if dest.IsNil() {
|
||||||
|
dest.Set(reflect.New(dest.Type().Elem()))
|
||||||
|
}
|
||||||
|
return store(dest.Elem(), src)
|
||||||
|
}
|
||||||
|
switch src.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
return storeSlice(dest, src)
|
||||||
|
case reflect.Map:
|
||||||
|
return storeMap(dest, src)
|
||||||
|
default:
|
||||||
|
return storeBase(dest, src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeBase(dest, src reflect.Value) error {
|
||||||
|
return setDest(dest, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setDest(dest, src reflect.Value) error {
|
||||||
|
if !isVariant(src.Type()) && isVariant(dest.Type()) {
|
||||||
|
// special conversion for dbus.Variant
|
||||||
|
dest.Set(reflect.ValueOf(MakeVariant(src.Interface())))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if isVariant(src.Type()) && !isVariant(dest.Type()) {
|
||||||
|
src = getVariantValue(src)
|
||||||
|
return store(dest, src)
|
||||||
|
}
|
||||||
|
if !src.Type().ConvertibleTo(dest.Type()) {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"dbus.Store: type mismatch: cannot convert %s to %s",
|
||||||
|
src.Type(), dest.Type())
|
||||||
|
}
|
||||||
|
dest.Set(src.Convert(dest.Type()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func kindsAreCompatible(dest, src reflect.Type) bool {
|
||||||
|
switch {
|
||||||
|
case isVariant(dest):
|
||||||
|
return true
|
||||||
|
case dest.Kind() == reflect.Interface:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return dest.Kind() == src.Kind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isConvertibleTo(dest, src reflect.Type) bool {
|
||||||
|
switch {
|
||||||
|
case isVariant(dest):
|
||||||
|
return true
|
||||||
|
case dest.Kind() == reflect.Interface:
|
||||||
|
return true
|
||||||
|
case dest.Kind() == reflect.Slice:
|
||||||
|
return src.Kind() == reflect.Slice &&
|
||||||
|
isConvertibleTo(dest.Elem(), src.Elem())
|
||||||
|
case dest.Kind() == reflect.Ptr:
|
||||||
|
dest = dest.Elem()
|
||||||
|
return isConvertibleTo(dest, src)
|
||||||
|
case dest.Kind() == reflect.Struct:
|
||||||
|
return src == interfacesType || dest.Kind() == src.Kind()
|
||||||
|
default:
|
||||||
|
return src.ConvertibleTo(dest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeMap(dest, src reflect.Value) error {
|
||||||
|
switch {
|
||||||
|
case !kindsAreCompatible(dest.Type(), src.Type()):
|
||||||
|
return fmt.Errorf(
|
||||||
|
"dbus.Store: type mismatch: "+
|
||||||
|
"map: cannot store a value of %s into %s",
|
||||||
|
src.Type(), dest.Type())
|
||||||
|
case isVariant(dest.Type()):
|
||||||
|
return storeMapIntoVariant(dest, src)
|
||||||
|
case dest.Kind() == reflect.Interface:
|
||||||
|
return storeMapIntoInterface(dest, src)
|
||||||
|
case isConvertibleTo(dest.Type().Key(), src.Type().Key()) &&
|
||||||
|
isConvertibleTo(dest.Type().Elem(), src.Type().Elem()):
|
||||||
|
return storeMapIntoMap(dest, src)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(
|
||||||
|
"dbus.Store: type mismatch: "+
|
||||||
|
"map: cannot convert a value of %s into %s",
|
||||||
|
src.Type(), dest.Type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeMapIntoVariant(dest, src reflect.Value) error {
|
||||||
|
dv := reflect.MakeMap(src.Type())
|
||||||
|
err := store(dv, src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return storeBase(dest, dv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeMapIntoInterface(dest, src reflect.Value) error {
|
||||||
|
var dv reflect.Value
|
||||||
|
if isVariant(src.Type().Elem()) {
|
||||||
|
// Convert variants to interface{} recursively when converting
|
||||||
|
// to interface{}
|
||||||
|
dv = reflect.MakeMap(
|
||||||
|
reflect.MapOf(src.Type().Key(), interfaceType))
|
||||||
|
} else {
|
||||||
|
dv = reflect.MakeMap(src.Type())
|
||||||
|
}
|
||||||
|
err := store(dv, src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return storeBase(dest, dv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeMapIntoMap(dest, src reflect.Value) error {
|
||||||
|
if dest.IsNil() {
|
||||||
|
dest.Set(reflect.MakeMap(dest.Type()))
|
||||||
|
}
|
||||||
|
keys := src.MapKeys()
|
||||||
|
for _, key := range keys {
|
||||||
|
dkey := key.Convert(dest.Type().Key())
|
||||||
|
dval := reflect.New(dest.Type().Elem()).Elem()
|
||||||
|
err := store(dval, getVariantValue(src.MapIndex(key)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dest.SetMapIndex(dkey, dval)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeSlice(dest, src reflect.Value) error {
|
||||||
|
switch {
|
||||||
|
case src.Type() == interfacesType && dest.Kind() == reflect.Struct:
|
||||||
|
// The decoder always decodes structs as slices of interface{}
|
||||||
|
return storeStruct(dest, src)
|
||||||
|
case !kindsAreCompatible(dest.Type(), src.Type()):
|
||||||
|
return fmt.Errorf(
|
||||||
|
"dbus.Store: type mismatch: "+
|
||||||
|
"slice: cannot store a value of %s into %s",
|
||||||
|
src.Type(), dest.Type())
|
||||||
|
case isVariant(dest.Type()):
|
||||||
|
return storeSliceIntoVariant(dest, src)
|
||||||
|
case dest.Kind() == reflect.Interface:
|
||||||
|
return storeSliceIntoInterface(dest, src)
|
||||||
|
case isConvertibleTo(dest.Type().Elem(), src.Type().Elem()):
|
||||||
|
return storeSliceIntoSlice(dest, src)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(
|
||||||
|
"dbus.Store: type mismatch: "+
|
||||||
|
"slice: cannot convert a value of %s into %s",
|
||||||
|
src.Type(), dest.Type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeStruct(dest, src reflect.Value) error {
|
||||||
|
if isVariant(dest.Type()) {
|
||||||
|
return storeBase(dest, src)
|
||||||
|
}
|
||||||
|
dval := make([]any, 0, dest.NumField())
|
||||||
|
dtype := dest.Type()
|
||||||
|
for i := 0; i < dest.NumField(); i++ {
|
||||||
|
field := dest.Field(i)
|
||||||
|
ftype := dtype.Field(i)
|
||||||
|
if ftype.PkgPath != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ftype.Tag.Get("dbus") == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dval = append(dval, field.Addr().Interface())
|
||||||
|
}
|
||||||
|
if src.Len() != len(dval) {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"dbus.Store: type mismatch: "+
|
||||||
|
"destination struct does not have "+
|
||||||
|
"enough fields need: %d have: %d",
|
||||||
|
src.Len(), len(dval))
|
||||||
|
}
|
||||||
|
return Store(src.Interface().([]any), dval...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeSliceIntoVariant(dest, src reflect.Value) error {
|
||||||
|
dv := reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
|
||||||
|
err := store(dv, src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return storeBase(dest, dv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeSliceIntoInterface(dest, src reflect.Value) error {
|
||||||
|
var dv reflect.Value
|
||||||
|
if isVariant(src.Type().Elem()) {
|
||||||
|
// Convert variants to interface{} recursively when converting
|
||||||
|
// to interface{}
|
||||||
|
dv = reflect.MakeSlice(reflect.SliceOf(interfaceType),
|
||||||
|
src.Len(), src.Cap())
|
||||||
|
} else {
|
||||||
|
dv = reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
|
||||||
|
}
|
||||||
|
err := store(dv, src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return storeBase(dest, dv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeSliceIntoSlice(dest, src reflect.Value) error {
|
||||||
|
if dest.IsNil() || dest.Len() < src.Len() {
|
||||||
|
dest.Set(reflect.MakeSlice(dest.Type(), src.Len(), src.Cap()))
|
||||||
|
} else if dest.Len() > src.Len() {
|
||||||
|
dest.Set(dest.Slice(0, src.Len()))
|
||||||
|
}
|
||||||
|
for i := 0; i < src.Len(); i++ {
|
||||||
|
err := store(dest.Index(i), getVariantValue(src.Index(i)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getVariantValue(in reflect.Value) reflect.Value {
|
||||||
|
if isVariant(in.Type()) {
|
||||||
|
return reflect.ValueOf(in.Interface().(Variant).Value())
|
||||||
|
}
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
func isVariant(t reflect.Type) bool {
|
||||||
|
return t == variantType
|
||||||
|
}
|
||||||
|
|
||||||
|
// An ObjectPath is an object path as defined by the D-Bus spec.
|
||||||
|
type ObjectPath string
|
||||||
|
|
||||||
|
// IsValid returns whether the object path is valid.
|
||||||
|
func (o ObjectPath) IsValid() bool {
|
||||||
|
s := string(o)
|
||||||
|
if len(s) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s[0] != '/' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s[len(s)-1] == '/' && len(s) != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// probably not used, but technically possible
|
||||||
|
if s == "/" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
split := strings.Split(s[1:], "/")
|
||||||
|
for _, v := range split {
|
||||||
|
if len(v) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, c := range v {
|
||||||
|
if !isMemberChar(c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// A UnixFD is a Unix file descriptor sent over the wire. See the package-level
|
||||||
|
// documentation for more information about Unix file descriptor passing.
|
||||||
|
type UnixFD int32
|
||||||
|
|
||||||
|
// A UnixFDIndex is the representation of a Unix file descriptor in a message.
|
||||||
|
type UnixFDIndex uint32
|
||||||
|
|
||||||
|
// alignment returns the alignment of values of type t.
|
||||||
|
func alignment(t reflect.Type) int {
|
||||||
|
switch t {
|
||||||
|
case variantType:
|
||||||
|
return 1
|
||||||
|
case objectPathType:
|
||||||
|
return 4
|
||||||
|
case signatureType:
|
||||||
|
return 1
|
||||||
|
case interfacesType:
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Uint8:
|
||||||
|
return 1
|
||||||
|
case reflect.Uint16, reflect.Int16:
|
||||||
|
return 2
|
||||||
|
case reflect.Uint, reflect.Int, reflect.Uint32, reflect.Int32, reflect.String, reflect.Array, reflect.Slice, reflect.Map:
|
||||||
|
return 4
|
||||||
|
case reflect.Uint64, reflect.Int64, reflect.Float64, reflect.Struct:
|
||||||
|
return 8
|
||||||
|
case reflect.Ptr:
|
||||||
|
return alignment(t.Elem())
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// isKeyType returns whether t is a valid type for a D-Bus dict.
|
||||||
|
func isKeyType(t reflect.Type) bool {
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||||
|
reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64,
|
||||||
|
reflect.String, reflect.Uint, reflect.Int:
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidInterface returns whether s is a valid name for an interface.
|
||||||
|
func isValidInterface(s string) bool {
|
||||||
|
if len(s) == 0 || len(s) > 255 || s[0] == '.' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
elem := strings.Split(s, ".")
|
||||||
|
if len(elem) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, v := range elem {
|
||||||
|
if len(v) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v[0] >= '0' && v[0] <= '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, c := range v {
|
||||||
|
if !isMemberChar(c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidMember returns whether s is a valid name for a member.
|
||||||
|
func isValidMember(s string) bool {
|
||||||
|
if len(s) == 0 || len(s) > 255 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
i := strings.Index(s, ".")
|
||||||
|
if i != -1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s[0] >= '0' && s[0] <= '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, c := range s {
|
||||||
|
if !isMemberChar(c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMemberChar(c rune) bool {
|
||||||
|
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
|
||||||
|
(c >= 'a' && c <= 'z') || c == '_'
|
||||||
|
}
|
||||||
376
vendor/github.com/godbus/dbus/v5/decoder.go
generated
vendored
Normal file
376
vendor/github.com/godbus/dbus/v5/decoder.go
generated
vendored
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type decoder struct {
|
||||||
|
in io.Reader
|
||||||
|
order binary.ByteOrder
|
||||||
|
pos int
|
||||||
|
fds []int
|
||||||
|
|
||||||
|
// The following fields are used to reduce memory allocs.
|
||||||
|
conv *stringConverter
|
||||||
|
buf []byte
|
||||||
|
d float64
|
||||||
|
y [1]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// newDecoder returns a new decoder that reads values from in. The input is
|
||||||
|
// expected to be in the given byte order.
|
||||||
|
func newDecoder(in io.Reader, order binary.ByteOrder, fds []int) *decoder {
|
||||||
|
dec := new(decoder)
|
||||||
|
dec.in = in
|
||||||
|
dec.order = order
|
||||||
|
dec.fds = fds
|
||||||
|
dec.conv = newStringConverter(stringConverterBufferSize)
|
||||||
|
return dec
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the decoder to be reading from in.
|
||||||
|
func (dec *decoder) Reset(in io.Reader, order binary.ByteOrder, fds []int) {
|
||||||
|
dec.in = in
|
||||||
|
dec.order = order
|
||||||
|
dec.pos = 0
|
||||||
|
dec.fds = fds
|
||||||
|
|
||||||
|
if dec.conv == nil {
|
||||||
|
dec.conv = newStringConverter(stringConverterBufferSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// align aligns the input to the given boundary and panics on error.
|
||||||
|
func (dec *decoder) align(n int) {
|
||||||
|
if dec.pos%n != 0 {
|
||||||
|
newpos := (dec.pos + n - 1) & ^(n - 1)
|
||||||
|
dec.read2buf(newpos - dec.pos)
|
||||||
|
dec.pos = newpos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calls binary.Read(dec.in, dec.order, v) and panics on read errors.
|
||||||
|
func (dec *decoder) binread(v any) {
|
||||||
|
if err := binary.Read(dec.in, dec.order, v); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *decoder) Decode(sig Signature) (vs []any, err error) {
|
||||||
|
defer func() {
|
||||||
|
var ok bool
|
||||||
|
v := recover()
|
||||||
|
if err, ok = v.(error); ok {
|
||||||
|
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||||
|
err = FormatError("unexpected EOF")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
vs = make([]any, 0)
|
||||||
|
s := sig.str
|
||||||
|
for s != "" {
|
||||||
|
err, rem := validSingle(s, &depthCounter{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
v := dec.decode(s[:len(s)-len(rem)], 0)
|
||||||
|
vs = append(vs, v)
|
||||||
|
s = rem
|
||||||
|
}
|
||||||
|
return vs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// read2buf reads exactly n bytes from the reader dec.in into the buffer dec.buf
|
||||||
|
// to reduce memory allocs.
|
||||||
|
// The buffer grows automatically.
|
||||||
|
func (dec *decoder) read2buf(n int) {
|
||||||
|
if cap(dec.buf) < n {
|
||||||
|
dec.buf = make([]byte, n)
|
||||||
|
} else {
|
||||||
|
dec.buf = dec.buf[:n]
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(dec.in, dec.buf); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeU decodes uint32 obtained from the reader dec.in.
|
||||||
|
// The goal is to reduce memory allocs.
|
||||||
|
func (dec *decoder) decodeU() uint32 {
|
||||||
|
dec.align(4)
|
||||||
|
dec.read2buf(4)
|
||||||
|
dec.pos += 4
|
||||||
|
return dec.order.Uint32(dec.buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *decoder) decode(s string, depth int) any {
|
||||||
|
dec.align(alignment(typeFor(s)))
|
||||||
|
switch s[0] {
|
||||||
|
case 'y':
|
||||||
|
if _, err := dec.in.Read(dec.y[:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
dec.pos++
|
||||||
|
return dec.y[0]
|
||||||
|
case 'b':
|
||||||
|
switch dec.decodeU() {
|
||||||
|
case 0:
|
||||||
|
return false
|
||||||
|
case 1:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
panic(FormatError("invalid value for boolean"))
|
||||||
|
}
|
||||||
|
case 'n':
|
||||||
|
dec.read2buf(2)
|
||||||
|
dec.pos += 2
|
||||||
|
return int16(dec.order.Uint16(dec.buf))
|
||||||
|
case 'i':
|
||||||
|
dec.read2buf(4)
|
||||||
|
dec.pos += 4
|
||||||
|
return int32(dec.order.Uint32(dec.buf))
|
||||||
|
case 'x':
|
||||||
|
dec.read2buf(8)
|
||||||
|
dec.pos += 8
|
||||||
|
return int64(dec.order.Uint64(dec.buf))
|
||||||
|
case 'q':
|
||||||
|
dec.read2buf(2)
|
||||||
|
dec.pos += 2
|
||||||
|
return dec.order.Uint16(dec.buf)
|
||||||
|
case 'u':
|
||||||
|
return dec.decodeU()
|
||||||
|
case 't':
|
||||||
|
dec.read2buf(8)
|
||||||
|
dec.pos += 8
|
||||||
|
return dec.order.Uint64(dec.buf)
|
||||||
|
case 'd':
|
||||||
|
dec.binread(&dec.d)
|
||||||
|
dec.pos += 8
|
||||||
|
return dec.d
|
||||||
|
case 's':
|
||||||
|
length := dec.decodeU()
|
||||||
|
p := int(length) + 1
|
||||||
|
dec.read2buf(p)
|
||||||
|
dec.pos += p
|
||||||
|
return dec.conv.String(dec.buf[:len(dec.buf)-1])
|
||||||
|
case 'o':
|
||||||
|
return ObjectPath(dec.decode("s", depth).(string))
|
||||||
|
case 'g':
|
||||||
|
length := dec.decode("y", depth).(byte)
|
||||||
|
p := int(length) + 1
|
||||||
|
dec.read2buf(p)
|
||||||
|
dec.pos += p
|
||||||
|
sig, err := ParseSignature(
|
||||||
|
dec.conv.String(dec.buf[:len(dec.buf)-1]),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return sig
|
||||||
|
case 'v':
|
||||||
|
if depth >= 64 {
|
||||||
|
panic(FormatError("input exceeds container depth limit"))
|
||||||
|
}
|
||||||
|
var variant Variant
|
||||||
|
sig := dec.decode("g", depth).(Signature)
|
||||||
|
if len(sig.str) == 0 {
|
||||||
|
panic(FormatError("variant signature is empty"))
|
||||||
|
}
|
||||||
|
err, rem := validSingle(sig.str, &depthCounter{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if rem != "" {
|
||||||
|
panic(FormatError("variant signature has multiple types"))
|
||||||
|
}
|
||||||
|
variant.sig = sig
|
||||||
|
variant.value = dec.decode(sig.str, depth+1)
|
||||||
|
return variant
|
||||||
|
case 'h':
|
||||||
|
idx := dec.decodeU()
|
||||||
|
if int(idx) < len(dec.fds) {
|
||||||
|
return UnixFD(dec.fds[idx])
|
||||||
|
}
|
||||||
|
return UnixFDIndex(idx)
|
||||||
|
case 'a':
|
||||||
|
if len(s) > 1 && s[1] == '{' {
|
||||||
|
ksig := s[2:3]
|
||||||
|
vsig := s[3 : len(s)-1]
|
||||||
|
v := reflect.MakeMap(reflect.MapOf(typeFor(ksig), typeFor(vsig)))
|
||||||
|
if depth >= 63 {
|
||||||
|
panic(FormatError("input exceeds container depth limit"))
|
||||||
|
}
|
||||||
|
length := dec.decodeU()
|
||||||
|
// Even for empty maps, the correct padding must be included
|
||||||
|
dec.align(8)
|
||||||
|
spos := dec.pos
|
||||||
|
for dec.pos < spos+int(length) {
|
||||||
|
dec.align(8)
|
||||||
|
if !isKeyType(v.Type().Key()) {
|
||||||
|
panic(InvalidTypeError{v.Type()})
|
||||||
|
}
|
||||||
|
kv := dec.decode(ksig, depth+2)
|
||||||
|
vv := dec.decode(vsig, depth+2)
|
||||||
|
v.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv))
|
||||||
|
}
|
||||||
|
return v.Interface()
|
||||||
|
}
|
||||||
|
if depth >= 64 {
|
||||||
|
panic(FormatError("input exceeds container depth limit"))
|
||||||
|
}
|
||||||
|
sig := s[1:]
|
||||||
|
length := dec.decodeU()
|
||||||
|
// capacity can be determined only for fixed-size element types
|
||||||
|
var capacity int
|
||||||
|
if s := sigByteSize(sig); s != 0 {
|
||||||
|
capacity = int(length) / s
|
||||||
|
}
|
||||||
|
v := reflect.MakeSlice(reflect.SliceOf(typeFor(sig)), 0, capacity)
|
||||||
|
// Even for empty arrays, the correct padding must be included
|
||||||
|
align := alignment(typeFor(s[1:]))
|
||||||
|
if len(s) > 1 && s[1] == '(' {
|
||||||
|
// Special case for arrays of structs
|
||||||
|
// structs decode as a slice of interface{} values
|
||||||
|
// but the dbus alignment does not match this
|
||||||
|
align = 8
|
||||||
|
}
|
||||||
|
dec.align(align)
|
||||||
|
spos := dec.pos
|
||||||
|
for dec.pos < spos+int(length) {
|
||||||
|
ev := dec.decode(s[1:], depth+1)
|
||||||
|
v = reflect.Append(v, reflect.ValueOf(ev))
|
||||||
|
}
|
||||||
|
return v.Interface()
|
||||||
|
case '(':
|
||||||
|
if depth >= 64 {
|
||||||
|
panic(FormatError("input exceeds container depth limit"))
|
||||||
|
}
|
||||||
|
dec.align(8)
|
||||||
|
v := make([]any, 0)
|
||||||
|
s = s[1 : len(s)-1]
|
||||||
|
for s != "" {
|
||||||
|
err, rem := validSingle(s, &depthCounter{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ev := dec.decode(s[:len(s)-len(rem)], depth+1)
|
||||||
|
v = append(v, ev)
|
||||||
|
s = rem
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
default:
|
||||||
|
panic(SignatureError{Sig: s})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sigByteSize tries to calculates size of the given signature in bytes.
|
||||||
|
//
|
||||||
|
// It returns zero when it can't, for example when it contains non-fixed size
|
||||||
|
// types such as strings, maps and arrays that require reading of the transmitted
|
||||||
|
// data, for that we would need to implement the unread method for Decoder first.
|
||||||
|
func sigByteSize(sig string) int {
|
||||||
|
var total int
|
||||||
|
for offset := 0; offset < len(sig); {
|
||||||
|
switch sig[offset] {
|
||||||
|
case 'y':
|
||||||
|
total += 1
|
||||||
|
offset += 1
|
||||||
|
case 'n', 'q':
|
||||||
|
total += 2
|
||||||
|
offset += 1
|
||||||
|
case 'b', 'i', 'u', 'h':
|
||||||
|
total += 4
|
||||||
|
offset += 1
|
||||||
|
case 'x', 't', 'd':
|
||||||
|
total += 8
|
||||||
|
offset += 1
|
||||||
|
case '(':
|
||||||
|
i := 1
|
||||||
|
depth := 1
|
||||||
|
for i < len(sig[offset:]) && depth != 0 {
|
||||||
|
switch sig[offset+i] {
|
||||||
|
case '(':
|
||||||
|
depth++
|
||||||
|
case ')':
|
||||||
|
depth--
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
s := sigByteSize(sig[offset+1 : offset+i-1])
|
||||||
|
if s == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
total += s
|
||||||
|
offset += i
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
// A FormatError is an error in the wire format.
|
||||||
|
type FormatError string
|
||||||
|
|
||||||
|
func (e FormatError) Error() string {
|
||||||
|
return "dbus: wire format error: " + string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringConverterBufferSize defines the recommended buffer size of 4KB.
|
||||||
|
// It showed good results in a benchmark when decoding 35KB message,
|
||||||
|
// see https://github.com/marselester/systemd#testing.
|
||||||
|
const stringConverterBufferSize = 4096
|
||||||
|
|
||||||
|
func newStringConverter(capacity int) *stringConverter {
|
||||||
|
return &stringConverter{
|
||||||
|
buf: make([]byte, 0, capacity),
|
||||||
|
offset: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringConverter converts bytes to strings with less allocs.
|
||||||
|
// The idea is to accumulate bytes in a buffer with specified capacity
|
||||||
|
// and create strings with unsafe package using bytes from a buffer.
|
||||||
|
// For example, 10 "fizz" strings written to a 40-byte buffer
|
||||||
|
// will result in 1 alloc instead of 10.
|
||||||
|
//
|
||||||
|
// Once a buffer is filled, a new one is created with the same capacity.
|
||||||
|
// Old buffers will be eventually GC-ed
|
||||||
|
// with no side effects to the returned strings.
|
||||||
|
type stringConverter struct {
|
||||||
|
// buf is a temporary buffer where decoded strings are batched.
|
||||||
|
buf []byte
|
||||||
|
// offset is a buffer position where the last string was written.
|
||||||
|
offset int
|
||||||
|
}
|
||||||
|
|
||||||
|
// String converts bytes to a string.
|
||||||
|
func (c *stringConverter) String(b []byte) string {
|
||||||
|
n := len(b)
|
||||||
|
if n == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// Must allocate because a string doesn't fit into the buffer.
|
||||||
|
if n > cap(c.buf) {
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.buf)+n > cap(c.buf) {
|
||||||
|
c.buf = make([]byte, 0, cap(c.buf))
|
||||||
|
c.offset = 0
|
||||||
|
}
|
||||||
|
c.buf = append(c.buf, b...)
|
||||||
|
|
||||||
|
b = c.buf[c.offset:]
|
||||||
|
s := toString(b)
|
||||||
|
c.offset += n
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// toString converts a byte slice to a string without allocating.
|
||||||
|
func toString(b []byte) string {
|
||||||
|
return unsafe.String(&b[0], len(b))
|
||||||
|
}
|
||||||
338
vendor/github.com/godbus/dbus/v5/default_handler.go
generated
vendored
Normal file
338
vendor/github.com/godbus/dbus/v5/default_handler.go
generated
vendored
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newIntrospectIntf(h *defaultHandler) *exportedIntf {
|
||||||
|
methods := make(map[string]Method)
|
||||||
|
methods["Introspect"] = exportedMethod{
|
||||||
|
reflect.ValueOf(func(msg Message) (string, *Error) {
|
||||||
|
path := msg.Headers[FieldPath].value.(ObjectPath)
|
||||||
|
return h.introspectPath(path), nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
return newExportedIntf(methods, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultHandler returns an instance of the default
|
||||||
|
// call handler. This is useful if you want to implement only
|
||||||
|
// one of the two handlers but not both.
|
||||||
|
//
|
||||||
|
// Deprecated: this is the default value, don't use it, it will be unexported.
|
||||||
|
func NewDefaultHandler() *defaultHandler {
|
||||||
|
h := &defaultHandler{
|
||||||
|
objects: make(map[ObjectPath]*exportedObj),
|
||||||
|
defaultIntf: make(map[string]*exportedIntf),
|
||||||
|
}
|
||||||
|
h.defaultIntf["org.freedesktop.DBus.Introspectable"] = newIntrospectIntf(h)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultHandler struct {
|
||||||
|
sync.RWMutex
|
||||||
|
objects map[ObjectPath]*exportedObj
|
||||||
|
defaultIntf map[string]*exportedIntf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *defaultHandler) PathExists(path ObjectPath) bool {
|
||||||
|
_, ok := h.objects[path]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *defaultHandler) introspectPath(path ObjectPath) string {
|
||||||
|
subpath := make(map[string]struct{})
|
||||||
|
var xml bytes.Buffer
|
||||||
|
xml.WriteString("<node>")
|
||||||
|
for obj := range h.objects {
|
||||||
|
p := string(path)
|
||||||
|
if p != "/" {
|
||||||
|
p += "/"
|
||||||
|
}
|
||||||
|
if after, ok := strings.CutPrefix(string(obj), p); ok {
|
||||||
|
name, _, _ := strings.Cut(after, "/")
|
||||||
|
subpath[name] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for s := range subpath {
|
||||||
|
xml.WriteString("\n\t<node name=\"" + s + "\"/>")
|
||||||
|
}
|
||||||
|
xml.WriteString("\n</node>")
|
||||||
|
return xml.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *defaultHandler) LookupObject(path ObjectPath) (ServerObject, bool) {
|
||||||
|
h.RLock()
|
||||||
|
defer h.RUnlock()
|
||||||
|
object, ok := h.objects[path]
|
||||||
|
if ok {
|
||||||
|
return object, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an object wasn't found for this exact path,
|
||||||
|
// look for a matching subtree registration
|
||||||
|
subtreeObject := newExportedObject()
|
||||||
|
path = path[:strings.LastIndex(string(path), "/")]
|
||||||
|
for len(path) > 0 {
|
||||||
|
object, ok = h.objects[path]
|
||||||
|
if ok {
|
||||||
|
for name, iface := range object.interfaces {
|
||||||
|
// Only include this handler if it registered for the subtree
|
||||||
|
if iface.isFallbackInterface() {
|
||||||
|
subtreeObject.interfaces[name] = iface
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
path = path[:strings.LastIndex(string(path), "/")]
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, intf := range h.defaultIntf {
|
||||||
|
if _, exists := subtreeObject.interfaces[name]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
subtreeObject.interfaces[name] = intf
|
||||||
|
}
|
||||||
|
|
||||||
|
return subtreeObject, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *defaultHandler) AddObject(path ObjectPath, object *exportedObj) {
|
||||||
|
h.Lock()
|
||||||
|
h.objects[path] = object
|
||||||
|
h.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *defaultHandler) DeleteObject(path ObjectPath) {
|
||||||
|
h.Lock()
|
||||||
|
delete(h.objects, path)
|
||||||
|
h.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
type exportedMethod struct {
|
||||||
|
reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m exportedMethod) Call(args ...any) ([]any, error) {
|
||||||
|
t := m.Type()
|
||||||
|
|
||||||
|
params := make([]reflect.Value, len(args))
|
||||||
|
for i := 0; i < len(args); i++ {
|
||||||
|
params[i] = reflect.ValueOf(args[i]).Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := m.Value.Call(params)
|
||||||
|
var err error
|
||||||
|
nilErr := false // The reflection will find almost-nils, let's only pass back clean ones!
|
||||||
|
if t.NumOut() > 0 {
|
||||||
|
if e, ok := ret[t.NumOut()-1].Interface().(*Error); ok { // godbus *Error
|
||||||
|
nilErr = ret[t.NumOut()-1].IsNil()
|
||||||
|
ret = ret[:t.NumOut()-1]
|
||||||
|
err = e
|
||||||
|
} else if ret[t.NumOut()-1].Type().Implements(errType) { // Go error
|
||||||
|
i := ret[t.NumOut()-1].Interface()
|
||||||
|
if i == nil {
|
||||||
|
nilErr = ret[t.NumOut()-1].IsNil()
|
||||||
|
} else {
|
||||||
|
err = i.(error)
|
||||||
|
}
|
||||||
|
ret = ret[:t.NumOut()-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out := make([]any, len(ret))
|
||||||
|
for i, val := range ret {
|
||||||
|
out[i] = val.Interface()
|
||||||
|
}
|
||||||
|
if nilErr || err == nil {
|
||||||
|
// concrete type to interface nil is a special case
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m exportedMethod) NumArguments() int {
|
||||||
|
return m.Value.Type().NumIn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m exportedMethod) ArgumentValue(i int) any {
|
||||||
|
return reflect.Zero(m.Type().In(i)).Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m exportedMethod) NumReturns() int {
|
||||||
|
return m.Value.Type().NumOut()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m exportedMethod) ReturnValue(i int) any {
|
||||||
|
return reflect.Zero(m.Type().Out(i)).Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExportedObject() *exportedObj {
|
||||||
|
return &exportedObj{
|
||||||
|
interfaces: make(map[string]*exportedIntf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type exportedObj struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
interfaces map[string]*exportedIntf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *exportedObj) LookupInterface(name string) (Interface, bool) {
|
||||||
|
if name == "" {
|
||||||
|
return obj, true
|
||||||
|
}
|
||||||
|
obj.mu.RLock()
|
||||||
|
defer obj.mu.RUnlock()
|
||||||
|
intf, exists := obj.interfaces[name]
|
||||||
|
return intf, exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *exportedObj) AddInterface(name string, iface *exportedIntf) {
|
||||||
|
obj.mu.Lock()
|
||||||
|
defer obj.mu.Unlock()
|
||||||
|
obj.interfaces[name] = iface
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *exportedObj) DeleteInterface(name string) {
|
||||||
|
obj.mu.Lock()
|
||||||
|
defer obj.mu.Unlock()
|
||||||
|
delete(obj.interfaces, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *exportedObj) LookupMethod(name string) (Method, bool) {
|
||||||
|
obj.mu.RLock()
|
||||||
|
defer obj.mu.RUnlock()
|
||||||
|
for _, intf := range obj.interfaces {
|
||||||
|
method, exists := intf.LookupMethod(name)
|
||||||
|
if exists {
|
||||||
|
return method, exists
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExportedIntf(methods map[string]Method, includeSubtree bool) *exportedIntf {
|
||||||
|
return &exportedIntf{
|
||||||
|
methods: methods,
|
||||||
|
includeSubtree: includeSubtree,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type exportedIntf struct {
|
||||||
|
methods map[string]Method
|
||||||
|
|
||||||
|
// Whether or not this export is for the entire subtree
|
||||||
|
includeSubtree bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *exportedIntf) LookupMethod(name string) (Method, bool) {
|
||||||
|
out, exists := obj.methods[name]
|
||||||
|
return out, exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *exportedIntf) isFallbackInterface() bool {
|
||||||
|
return obj.includeSubtree
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultSignalHandler returns an instance of the default
|
||||||
|
// signal handler. This is useful if you want to implement only
|
||||||
|
// one of the two handlers but not both.
|
||||||
|
//
|
||||||
|
// Deprecated: this is the default value, don't use it, it will be unexported.
|
||||||
|
func NewDefaultSignalHandler() *defaultSignalHandler {
|
||||||
|
return &defaultSignalHandler{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultSignalHandler struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
closed bool
|
||||||
|
signals []*signalChannelData
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh *defaultSignalHandler) DeliverSignal(intf, name string, signal *Signal) {
|
||||||
|
sh.mu.RLock()
|
||||||
|
defer sh.mu.RUnlock()
|
||||||
|
if sh.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, scd := range sh.signals {
|
||||||
|
scd.deliver(signal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh *defaultSignalHandler) Terminate() {
|
||||||
|
sh.mu.Lock()
|
||||||
|
defer sh.mu.Unlock()
|
||||||
|
if sh.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, scd := range sh.signals {
|
||||||
|
scd.close()
|
||||||
|
close(scd.ch)
|
||||||
|
}
|
||||||
|
sh.closed = true
|
||||||
|
sh.signals = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh *defaultSignalHandler) AddSignal(ch chan<- *Signal) {
|
||||||
|
sh.mu.Lock()
|
||||||
|
defer sh.mu.Unlock()
|
||||||
|
if sh.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sh.signals = append(sh.signals, &signalChannelData{
|
||||||
|
ch: ch,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh *defaultSignalHandler) RemoveSignal(ch chan<- *Signal) {
|
||||||
|
sh.mu.Lock()
|
||||||
|
defer sh.mu.Unlock()
|
||||||
|
if sh.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := len(sh.signals) - 1; i >= 0; i-- {
|
||||||
|
if ch == sh.signals[i].ch {
|
||||||
|
sh.signals[i].close()
|
||||||
|
copy(sh.signals[i:], sh.signals[i+1:])
|
||||||
|
sh.signals[len(sh.signals)-1] = nil
|
||||||
|
sh.signals = sh.signals[:len(sh.signals)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type signalChannelData struct {
|
||||||
|
wg sync.WaitGroup
|
||||||
|
ch chan<- *Signal
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scd *signalChannelData) deliver(signal *Signal) {
|
||||||
|
select {
|
||||||
|
case scd.ch <- signal:
|
||||||
|
case <-scd.done:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
scd.wg.Add(1)
|
||||||
|
go scd.deferredDeliver(signal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scd *signalChannelData) deferredDeliver(signal *Signal) {
|
||||||
|
select {
|
||||||
|
case scd.ch <- signal:
|
||||||
|
case <-scd.done:
|
||||||
|
}
|
||||||
|
scd.wg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scd *signalChannelData) close() {
|
||||||
|
close(scd.done)
|
||||||
|
scd.wg.Wait() // wait until all spawned goroutines return
|
||||||
|
}
|
||||||
70
vendor/github.com/godbus/dbus/v5/doc.go
generated
vendored
Normal file
70
vendor/github.com/godbus/dbus/v5/doc.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
Package dbus implements bindings to the D-Bus message bus system.
|
||||||
|
|
||||||
|
To use the message bus API, you first need to connect to a bus (usually the
|
||||||
|
session or system bus). The acquired connection then can be used to call methods
|
||||||
|
on remote objects and emit or receive signals. Using the Export method, you can
|
||||||
|
arrange D-Bus methods calls to be directly translated to method calls on a Go
|
||||||
|
value.
|
||||||
|
|
||||||
|
# Conversion Rules
|
||||||
|
|
||||||
|
For outgoing messages, Go types are automatically converted to the
|
||||||
|
corresponding D-Bus types. See the official specification at
|
||||||
|
https://dbus.freedesktop.org/doc/dbus-specification.html#type-system for more
|
||||||
|
information on the D-Bus type system. The following types are directly encoded
|
||||||
|
as their respective D-Bus equivalents:
|
||||||
|
|
||||||
|
Go type | D-Bus type
|
||||||
|
------------+-----------
|
||||||
|
byte | BYTE
|
||||||
|
bool | BOOLEAN
|
||||||
|
int16 | INT16
|
||||||
|
uint16 | UINT16
|
||||||
|
int | INT32
|
||||||
|
uint | UINT32
|
||||||
|
int32 | INT32
|
||||||
|
uint32 | UINT32
|
||||||
|
int64 | INT64
|
||||||
|
uint64 | UINT64
|
||||||
|
float64 | DOUBLE
|
||||||
|
string | STRING
|
||||||
|
ObjectPath | OBJECT_PATH
|
||||||
|
Signature | SIGNATURE
|
||||||
|
Variant | VARIANT
|
||||||
|
interface{} | VARIANT
|
||||||
|
UnixFDIndex | UNIX_FD
|
||||||
|
|
||||||
|
Slices and arrays encode as ARRAYs of their element type.
|
||||||
|
|
||||||
|
Maps encode as DICTs, provided that their key type can be used as a key for
|
||||||
|
a DICT.
|
||||||
|
|
||||||
|
Structs other than Variant and Signature encode as a STRUCT containing their
|
||||||
|
exported fields in order. Fields whose tags contain `dbus:"-"` and unexported
|
||||||
|
fields will be skipped.
|
||||||
|
|
||||||
|
Pointers encode as the value they're pointed to.
|
||||||
|
|
||||||
|
Types convertible to one of the base types above will be mapped as the
|
||||||
|
base type.
|
||||||
|
|
||||||
|
Trying to encode any other type or a slice, map or struct containing an
|
||||||
|
unsupported type will result in an InvalidTypeError.
|
||||||
|
|
||||||
|
For incoming messages, the inverse of these rules are used, with the exception
|
||||||
|
of STRUCTs. Incoming STRUCTS are represented as a slice of empty interfaces
|
||||||
|
containing the struct fields in the correct order. The Store function can be
|
||||||
|
used to convert such values to Go structs.
|
||||||
|
|
||||||
|
# Unix FD passing
|
||||||
|
|
||||||
|
Handling Unix file descriptors deserves special mention. To use them, you should
|
||||||
|
first check that they are supported on a connection by calling SupportsUnixFDs.
|
||||||
|
If it returns true, all method of Connection will translate messages containing
|
||||||
|
UnixFD's to messages that are accompanied by the given file descriptors with the
|
||||||
|
UnixFD values being substituted by the correct indices. Similarly, the indices
|
||||||
|
of incoming messages are automatically resolved. It shouldn't be necessary to use
|
||||||
|
UnixFDIndex.
|
||||||
|
*/
|
||||||
|
package dbus
|
||||||
235
vendor/github.com/godbus/dbus/v5/encoder.go
generated
vendored
Normal file
235
vendor/github.com/godbus/dbus/v5/encoder.go
generated
vendored
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An encoder encodes values to the D-Bus wire format.
|
||||||
|
type encoder struct {
|
||||||
|
out io.Writer
|
||||||
|
fds []int
|
||||||
|
order binary.ByteOrder
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncoder returns a new encoder that writes to out in the given byte order.
|
||||||
|
func newEncoder(out io.Writer, order binary.ByteOrder, fds []int) *encoder {
|
||||||
|
enc := newEncoderAtOffset(out, 0, order, fds)
|
||||||
|
return enc
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEncoderAtOffset returns a new encoder that writes to out in the given
|
||||||
|
// byte order. Specify the offset to initialize pos for proper alignment
|
||||||
|
// computation.
|
||||||
|
func newEncoderAtOffset(out io.Writer, offset int, order binary.ByteOrder, fds []int) *encoder {
|
||||||
|
enc := new(encoder)
|
||||||
|
enc.out = out
|
||||||
|
enc.order = order
|
||||||
|
enc.pos = offset
|
||||||
|
enc.fds = fds
|
||||||
|
return enc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aligns the next output to be on a multiple of n. Panics on write errors.
|
||||||
|
func (enc *encoder) align(n int) {
|
||||||
|
pad := enc.padding(0, n)
|
||||||
|
if pad > 0 {
|
||||||
|
empty := make([]byte, pad)
|
||||||
|
if _, err := enc.out.Write(empty); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
enc.pos += pad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pad returns the number of bytes of padding, based on current position and additional offset.
|
||||||
|
// and alignment.
|
||||||
|
func (enc *encoder) padding(offset, algn int) int {
|
||||||
|
abs := enc.pos + offset
|
||||||
|
if abs%algn != 0 {
|
||||||
|
newabs := (abs + algn - 1) & ^(algn - 1)
|
||||||
|
return newabs - abs
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calls binary.Write(enc.out, enc.order, v) and panics on write errors.
|
||||||
|
func (enc *encoder) binwrite(v any) {
|
||||||
|
if err := binary.Write(enc.out, enc.order, v); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode encodes the given values to the underlying reader. All written values
|
||||||
|
// are aligned properly as required by the D-Bus spec.
|
||||||
|
func (enc *encoder) Encode(vs ...any) (err error) {
|
||||||
|
defer func() {
|
||||||
|
err, _ = recover().(error)
|
||||||
|
}()
|
||||||
|
for _, v := range vs {
|
||||||
|
enc.encode(reflect.ValueOf(v), 0)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode encodes the given value to the writer and panics on error. depth holds
|
||||||
|
// the depth of the container nesting.
|
||||||
|
func (enc *encoder) encode(v reflect.Value, depth int) {
|
||||||
|
if depth > 64 {
|
||||||
|
panic(FormatError("input exceeds depth limitation"))
|
||||||
|
}
|
||||||
|
enc.align(alignment(v.Type()))
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Uint8:
|
||||||
|
var b [1]byte
|
||||||
|
b[0] = byte(v.Uint())
|
||||||
|
if _, err := enc.out.Write(b[:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
enc.pos++
|
||||||
|
case reflect.Bool:
|
||||||
|
if v.Bool() {
|
||||||
|
enc.encode(reflect.ValueOf(uint32(1)), depth)
|
||||||
|
} else {
|
||||||
|
enc.encode(reflect.ValueOf(uint32(0)), depth)
|
||||||
|
}
|
||||||
|
case reflect.Int16:
|
||||||
|
enc.binwrite(int16(v.Int()))
|
||||||
|
enc.pos += 2
|
||||||
|
case reflect.Uint16:
|
||||||
|
enc.binwrite(uint16(v.Uint()))
|
||||||
|
enc.pos += 2
|
||||||
|
case reflect.Int, reflect.Int32:
|
||||||
|
if v.Type() == unixFDType {
|
||||||
|
fd := v.Int()
|
||||||
|
idx := len(enc.fds)
|
||||||
|
enc.fds = append(enc.fds, int(fd))
|
||||||
|
enc.binwrite(uint32(idx))
|
||||||
|
} else {
|
||||||
|
enc.binwrite(int32(v.Int()))
|
||||||
|
}
|
||||||
|
enc.pos += 4
|
||||||
|
case reflect.Uint, reflect.Uint32:
|
||||||
|
enc.binwrite(uint32(v.Uint()))
|
||||||
|
enc.pos += 4
|
||||||
|
case reflect.Int64:
|
||||||
|
enc.binwrite(v.Int())
|
||||||
|
enc.pos += 8
|
||||||
|
case reflect.Uint64:
|
||||||
|
enc.binwrite(v.Uint())
|
||||||
|
enc.pos += 8
|
||||||
|
case reflect.Float64:
|
||||||
|
enc.binwrite(v.Float())
|
||||||
|
enc.pos += 8
|
||||||
|
case reflect.String:
|
||||||
|
str := v.String()
|
||||||
|
if !utf8.ValidString(str) {
|
||||||
|
panic(FormatError("input has a not-utf8 char in string"))
|
||||||
|
}
|
||||||
|
if strings.IndexByte(str, byte(0)) != -1 {
|
||||||
|
panic(FormatError("input has a null char('\\000') in string"))
|
||||||
|
}
|
||||||
|
if v.Type() == objectPathType {
|
||||||
|
if !ObjectPath(str).IsValid() {
|
||||||
|
panic(FormatError("invalid object path"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enc.encode(reflect.ValueOf(uint32(len(str))), depth)
|
||||||
|
b := make([]byte, v.Len()+1)
|
||||||
|
copy(b, str)
|
||||||
|
b[len(b)-1] = 0
|
||||||
|
n, err := enc.out.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
enc.pos += n
|
||||||
|
case reflect.Ptr:
|
||||||
|
enc.encode(v.Elem(), depth)
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
// Lookahead offset: 4 bytes for uint32 length (with alignment),
|
||||||
|
// plus alignment for elements.
|
||||||
|
n := enc.padding(0, 4) + 4
|
||||||
|
offset := enc.pos + n + enc.padding(n, alignment(v.Type().Elem()))
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
bufenc := newEncoderAtOffset(&buf, offset, enc.order, enc.fds)
|
||||||
|
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
bufenc.encode(v.Index(i), depth+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf.Len() > 1<<26 {
|
||||||
|
panic(FormatError("input exceeds array size limitation"))
|
||||||
|
}
|
||||||
|
|
||||||
|
enc.fds = bufenc.fds
|
||||||
|
enc.encode(reflect.ValueOf(uint32(buf.Len())), depth)
|
||||||
|
length := buf.Len()
|
||||||
|
enc.align(alignment(v.Type().Elem()))
|
||||||
|
if _, err := buf.WriteTo(enc.out); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
enc.pos += length
|
||||||
|
case reflect.Struct:
|
||||||
|
switch t := v.Type(); t {
|
||||||
|
case signatureType:
|
||||||
|
str := v.Field(0)
|
||||||
|
enc.encode(reflect.ValueOf(byte(str.Len())), depth)
|
||||||
|
b := make([]byte, str.Len()+1)
|
||||||
|
copy(b, str.String())
|
||||||
|
b[len(b)-1] = 0
|
||||||
|
n, err := enc.out.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
enc.pos += n
|
||||||
|
case variantType:
|
||||||
|
variant := v.Interface().(Variant)
|
||||||
|
enc.encode(reflect.ValueOf(variant.sig), depth+1)
|
||||||
|
enc.encode(reflect.ValueOf(variant.value), depth+1)
|
||||||
|
default:
|
||||||
|
for i := 0; i < v.Type().NumField(); i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
|
||||||
|
enc.encode(v.Field(i), depth+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
// Maps are arrays of structures, so they actually increase the depth by
|
||||||
|
// 2.
|
||||||
|
if !isKeyType(v.Type().Key()) {
|
||||||
|
panic(InvalidTypeError{v.Type()})
|
||||||
|
}
|
||||||
|
keys := v.MapKeys()
|
||||||
|
// Lookahead offset: 4 bytes for uint32 length (with alignment),
|
||||||
|
// plus 8-byte alignment
|
||||||
|
n := enc.padding(0, 4) + 4
|
||||||
|
offset := enc.pos + n + enc.padding(n, 8)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
bufenc := newEncoderAtOffset(&buf, offset, enc.order, enc.fds)
|
||||||
|
for _, k := range keys {
|
||||||
|
bufenc.align(8)
|
||||||
|
bufenc.encode(k, depth+2)
|
||||||
|
bufenc.encode(v.MapIndex(k), depth+2)
|
||||||
|
}
|
||||||
|
enc.fds = bufenc.fds
|
||||||
|
enc.encode(reflect.ValueOf(uint32(buf.Len())), depth)
|
||||||
|
length := buf.Len()
|
||||||
|
enc.align(8)
|
||||||
|
if _, err := buf.WriteTo(enc.out); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
enc.pos += length
|
||||||
|
case reflect.Interface:
|
||||||
|
enc.encode(reflect.ValueOf(MakeVariant(v.Interface())), depth)
|
||||||
|
default:
|
||||||
|
panic(InvalidTypeError{v.Type()})
|
||||||
|
}
|
||||||
|
}
|
||||||
84
vendor/github.com/godbus/dbus/v5/escape.go
generated
vendored
Normal file
84
vendor/github.com/godbus/dbus/v5/escape.go
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import "net/url"
|
||||||
|
|
||||||
|
// EscapeBusAddressValue implements a requirement to escape the values
|
||||||
|
// in D-Bus server addresses, as defined by the D-Bus specification at
|
||||||
|
// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses.
|
||||||
|
func EscapeBusAddressValue(val string) string {
|
||||||
|
toEsc := strNeedsEscape(val)
|
||||||
|
if toEsc == 0 {
|
||||||
|
// Avoid unneeded allocation/copying.
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid allocation for short paths.
|
||||||
|
var buf [64]byte
|
||||||
|
var out []byte
|
||||||
|
// Every to-be-escaped byte needs 2 extra bytes.
|
||||||
|
required := len(val) + 2*toEsc
|
||||||
|
if required <= len(buf) {
|
||||||
|
out = buf[:required]
|
||||||
|
} else {
|
||||||
|
out = make([]byte, required)
|
||||||
|
}
|
||||||
|
|
||||||
|
j := 0
|
||||||
|
for i := 0; i < len(val); i++ {
|
||||||
|
if ch := val[i]; needsEscape(ch) {
|
||||||
|
// Convert ch to %xx, where xx is hex value.
|
||||||
|
out[j] = '%'
|
||||||
|
out[j+1] = hexchar(ch >> 4)
|
||||||
|
out[j+2] = hexchar(ch & 0x0F)
|
||||||
|
j += 3
|
||||||
|
} else {
|
||||||
|
out[j] = ch
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnescapeBusAddressValue unescapes values in D-Bus server addresses,
|
||||||
|
// as defined by the D-Bus specification at
|
||||||
|
// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses.
|
||||||
|
func UnescapeBusAddressValue(val string) (string, error) {
|
||||||
|
// Looks like url.PathUnescape does exactly what is required.
|
||||||
|
return url.PathUnescape(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hexchar returns an octal representation of a n, where n < 16.
|
||||||
|
// For invalid values of n, the function panics.
|
||||||
|
func hexchar(n byte) byte {
|
||||||
|
const hex = "0123456789abcdef"
|
||||||
|
|
||||||
|
// For n >= len(hex), runtime will panic.
|
||||||
|
return hex[n]
|
||||||
|
}
|
||||||
|
|
||||||
|
// needsEscape tells if a byte is NOT one of optionally-escaped bytes.
|
||||||
|
func needsEscape(c byte) bool {
|
||||||
|
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch c {
|
||||||
|
case '-', '_', '/', '\\', '.', '*':
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// strNeedsEscape tells how many bytes in the string need escaping.
|
||||||
|
func strNeedsEscape(val string) int {
|
||||||
|
count := 0
|
||||||
|
|
||||||
|
for i := 0; i < len(val); i++ {
|
||||||
|
if needsEscape(val[i]) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
484
vendor/github.com/godbus/dbus/v5/export.go
generated
vendored
Normal file
484
vendor/github.com/godbus/dbus/v5/export.go
generated
vendored
Normal file
@@ -0,0 +1,484 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrMsgInvalidArg = Error{
|
||||||
|
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||||
|
[]any{"Invalid type / number of args"},
|
||||||
|
}
|
||||||
|
ErrMsgNoObject = Error{
|
||||||
|
"org.freedesktop.DBus.Error.NoSuchObject",
|
||||||
|
[]any{"No such object"},
|
||||||
|
}
|
||||||
|
ErrMsgUnknownMethod = Error{
|
||||||
|
"org.freedesktop.DBus.Error.UnknownMethod",
|
||||||
|
[]any{"Unknown / invalid method"},
|
||||||
|
}
|
||||||
|
ErrMsgUnknownInterface = Error{
|
||||||
|
"org.freedesktop.DBus.Error.UnknownInterface",
|
||||||
|
[]any{"Object does not implement the interface"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func MakeNoObjectError(path ObjectPath) Error {
|
||||||
|
return Error{
|
||||||
|
"org.freedesktop.DBus.Error.NoSuchObject",
|
||||||
|
[]any{fmt.Sprintf("No such object '%s'", string(path))},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeUnknownMethodError(methodName string) Error {
|
||||||
|
return Error{
|
||||||
|
"org.freedesktop.DBus.Error.UnknownMethod",
|
||||||
|
[]any{fmt.Sprintf("Unknown / invalid method '%s'", methodName)},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeUnknownInterfaceError(ifaceName string) Error {
|
||||||
|
return Error{
|
||||||
|
"org.freedesktop.DBus.Error.UnknownInterface",
|
||||||
|
[]any{fmt.Sprintf("Object does not implement the interface '%s'", ifaceName)},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeFailedError(err error) *Error {
|
||||||
|
return &Error{
|
||||||
|
"org.freedesktop.DBus.Error.Failed",
|
||||||
|
[]any{err.Error()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sender is a type which can be used in exported methods to receive the message
|
||||||
|
// sender.
|
||||||
|
type Sender string
|
||||||
|
|
||||||
|
func computeMethodName(name string, mapping map[string]string) string {
|
||||||
|
newname, ok := mapping[name]
|
||||||
|
if ok {
|
||||||
|
name = newname
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMethods(in any, mapping map[string]string) map[string]reflect.Value {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
methods := make(map[string]reflect.Value)
|
||||||
|
val := reflect.ValueOf(in)
|
||||||
|
typ := val.Type()
|
||||||
|
for i := 0; i < typ.NumMethod(); i++ {
|
||||||
|
methtype := typ.Method(i)
|
||||||
|
method := val.Method(i)
|
||||||
|
t := method.Type()
|
||||||
|
// only track valid methods must return *Error as last arg
|
||||||
|
// and must be exported
|
||||||
|
if t.NumOut() == 0 ||
|
||||||
|
t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) ||
|
||||||
|
methtype.PkgPath != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// map names while building table
|
||||||
|
methods[computeMethodName(methtype.Name, mapping)] = method
|
||||||
|
}
|
||||||
|
return methods
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAllMethods(in any, mapping map[string]string) map[string]reflect.Value {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
methods := make(map[string]reflect.Value)
|
||||||
|
val := reflect.ValueOf(in)
|
||||||
|
typ := val.Type()
|
||||||
|
for i := 0; i < typ.NumMethod(); i++ {
|
||||||
|
methtype := typ.Method(i)
|
||||||
|
method := val.Method(i)
|
||||||
|
// map names while building table
|
||||||
|
methods[computeMethodName(methtype.Name, mapping)] = method
|
||||||
|
}
|
||||||
|
return methods
|
||||||
|
}
|
||||||
|
|
||||||
|
func standardMethodArgumentDecode(m Method, sender string, msg *Message, body []any) ([]any, error) {
|
||||||
|
pointers := make([]any, m.NumArguments())
|
||||||
|
decode := make([]any, 0, len(body))
|
||||||
|
|
||||||
|
for i := 0; i < m.NumArguments(); i++ {
|
||||||
|
tp := reflect.TypeOf(m.ArgumentValue(i))
|
||||||
|
val := reflect.New(tp)
|
||||||
|
pointers[i] = val.Interface()
|
||||||
|
if tp == reflect.TypeOf((*Sender)(nil)).Elem() {
|
||||||
|
val.Elem().SetString(sender)
|
||||||
|
} else if tp == reflect.TypeOf((*Message)(nil)).Elem() {
|
||||||
|
val.Elem().Set(reflect.ValueOf(*msg))
|
||||||
|
} else {
|
||||||
|
decode = append(decode, pointers[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(decode) != len(body) {
|
||||||
|
return nil, ErrMsgInvalidArg
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Store(body, decode...); err != nil {
|
||||||
|
return nil, ErrMsgInvalidArg
|
||||||
|
}
|
||||||
|
|
||||||
|
return pointers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *Conn) decodeArguments(m Method, sender string, msg *Message) ([]any, error) {
|
||||||
|
if decoder, ok := m.(ArgumentDecoder); ok {
|
||||||
|
return decoder.DecodeArguments(conn, sender, msg, msg.Body)
|
||||||
|
}
|
||||||
|
return standardMethodArgumentDecode(m, sender, msg, msg.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleCall handles the given method call (i.e. looks if it's one of the
|
||||||
|
// pre-implemented ones and searches for a corresponding handler if not).
|
||||||
|
func (conn *Conn) handleCall(msg *Message) {
|
||||||
|
name := msg.Headers[FieldMember].value.(string)
|
||||||
|
path := msg.Headers[FieldPath].value.(ObjectPath)
|
||||||
|
ifaceName, _ := msg.Headers[FieldInterface].value.(string)
|
||||||
|
sender, hasSender := msg.Headers[FieldSender].value.(string)
|
||||||
|
serial := msg.serial
|
||||||
|
|
||||||
|
if len(name) == 0 {
|
||||||
|
conn.sendError(ErrMsgUnknownMethod, sender, serial)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ifaceName == "org.freedesktop.DBus.Peer" {
|
||||||
|
switch name {
|
||||||
|
case "Ping":
|
||||||
|
conn.sendReply(sender, serial)
|
||||||
|
case "GetMachineId":
|
||||||
|
conn.sendReply(sender, serial, conn.uuid)
|
||||||
|
default:
|
||||||
|
conn.sendError(MakeUnknownMethodError(name), sender, serial)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
object, ok := conn.handler.LookupObject(path)
|
||||||
|
if !ok {
|
||||||
|
conn.sendError(MakeNoObjectError(path), sender, serial)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
iface, exists := object.LookupInterface(ifaceName)
|
||||||
|
if !exists {
|
||||||
|
conn.sendError(MakeUnknownInterfaceError(ifaceName), sender, serial)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m, exists := iface.LookupMethod(name)
|
||||||
|
if !exists {
|
||||||
|
conn.sendError(MakeUnknownMethodError(name), sender, serial)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
args, err := conn.decodeArguments(m, sender, msg)
|
||||||
|
if err != nil {
|
||||||
|
conn.sendError(err, sender, serial)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := m.Call(args...)
|
||||||
|
if err != nil {
|
||||||
|
conn.sendError(err, sender, serial)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.Flags&FlagNoReplyExpected == 0 {
|
||||||
|
reply := new(Message)
|
||||||
|
reply.Type = TypeMethodReply
|
||||||
|
reply.Headers = make(map[HeaderField]Variant)
|
||||||
|
if hasSender {
|
||||||
|
reply.Headers[FieldDestination] = msg.Headers[FieldSender]
|
||||||
|
}
|
||||||
|
reply.Headers[FieldReplySerial] = MakeVariant(msg.serial)
|
||||||
|
reply.Body = make([]any, len(ret))
|
||||||
|
copy(reply.Body, ret)
|
||||||
|
reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...))
|
||||||
|
|
||||||
|
if err := conn.sendMessageAndIfClosed(reply, nil); err != nil {
|
||||||
|
if _, ok := err.(FormatError); ok {
|
||||||
|
fmt.Fprintf(os.Stderr, "dbus: replacing invalid reply to %s.%s on obj %s: %s\n", ifaceName, name, path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit emits the given signal on the message bus. The name parameter must be
|
||||||
|
// formatted as "interface.member", e.g., "org.freedesktop.DBus.NameLost".
|
||||||
|
func (conn *Conn) Emit(path ObjectPath, name string, values ...any) error {
|
||||||
|
i := strings.LastIndex(name, ".")
|
||||||
|
if i == -1 {
|
||||||
|
return errors.New("dbus: invalid method name")
|
||||||
|
}
|
||||||
|
iface := name[:i]
|
||||||
|
member := name[i+1:]
|
||||||
|
msg := new(Message)
|
||||||
|
msg.Type = TypeSignal
|
||||||
|
msg.Headers = make(map[HeaderField]Variant)
|
||||||
|
msg.Headers[FieldInterface] = MakeVariant(iface)
|
||||||
|
msg.Headers[FieldMember] = MakeVariant(member)
|
||||||
|
msg.Headers[FieldPath] = MakeVariant(path)
|
||||||
|
msg.Body = values
|
||||||
|
if len(values) > 0 {
|
||||||
|
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
|
||||||
|
}
|
||||||
|
|
||||||
|
var closed bool
|
||||||
|
err := conn.sendMessageAndIfClosed(msg, func() {
|
||||||
|
closed = true
|
||||||
|
})
|
||||||
|
if closed {
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export registers the given value to be exported as an object on the
|
||||||
|
// message bus.
|
||||||
|
//
|
||||||
|
// If a method call on the given path and interface is received, an exported
|
||||||
|
// method with the same name is called with v as the receiver if the
|
||||||
|
// parameters match and the last return value is of type *Error. If this
|
||||||
|
// *Error is not nil, it is sent back to the caller as an error.
|
||||||
|
// Otherwise, a method reply is sent with the other return values as its body.
|
||||||
|
//
|
||||||
|
// Any parameters with the special type Sender are set to the sender of the
|
||||||
|
// dbus message when the method is called. Parameters of this type do not
|
||||||
|
// contribute to the dbus signature of the method (i.e. the method is exposed
|
||||||
|
// as if the parameters of type Sender were not there).
|
||||||
|
//
|
||||||
|
// Similarly, any parameters with the type Message are set to the raw message
|
||||||
|
// received on the bus. Again, parameters of this type do not contribute to the
|
||||||
|
// dbus signature of the method.
|
||||||
|
//
|
||||||
|
// Every method call is executed in a new goroutine, so the method may be called
|
||||||
|
// in multiple goroutines at once.
|
||||||
|
//
|
||||||
|
// Method calls on the interface org.freedesktop.DBus.Peer will be automatically
|
||||||
|
// handled for every object.
|
||||||
|
//
|
||||||
|
// Passing nil as the first parameter will cause conn to cease handling calls on
|
||||||
|
// the given combination of path and interface.
|
||||||
|
//
|
||||||
|
// Export returns an error if path is not a valid path name.
|
||||||
|
func (conn *Conn) Export(v any, path ObjectPath, iface string) error {
|
||||||
|
return conn.ExportWithMap(v, nil, path, iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportAll registers all exported methods defined by the given object on
|
||||||
|
// the message bus.
|
||||||
|
//
|
||||||
|
// Unlike Export there is no requirement to have the last parameter as type
|
||||||
|
// *Error. If you want to be able to return error then you can append an error
|
||||||
|
// type parameter to your method signature. If the error returned is not nil,
|
||||||
|
// it is sent back to the caller as an error. Otherwise, a method reply is
|
||||||
|
// sent with the other return values as its body.
|
||||||
|
func (conn *Conn) ExportAll(v any, path ObjectPath, iface string) error {
|
||||||
|
return conn.export(getAllMethods(v, nil), path, iface, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportWithMap works exactly like Export but provides the ability to remap
|
||||||
|
// method names (e.g. export a lower-case method).
|
||||||
|
//
|
||||||
|
// The keys in the map are the real method names (exported on the struct), and
|
||||||
|
// the values are the method names to be exported on DBus.
|
||||||
|
func (conn *Conn) ExportWithMap(v any, mapping map[string]string, path ObjectPath, iface string) error {
|
||||||
|
return conn.export(getMethods(v, mapping), path, iface, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportSubtree works exactly like Export but registers the given value for
|
||||||
|
// an entire subtree rather under the root path provided.
|
||||||
|
//
|
||||||
|
// In order to make this useful, one parameter in each of the value's exported
|
||||||
|
// methods should be a Message, in which case it will contain the raw message
|
||||||
|
// (allowing one to get access to the path that caused the method to be called).
|
||||||
|
//
|
||||||
|
// Note that more specific export paths take precedence over less specific. For
|
||||||
|
// example, a method call using the ObjectPath /foo/bar/baz will call a method
|
||||||
|
// exported on /foo/bar before a method exported on /foo.
|
||||||
|
func (conn *Conn) ExportSubtree(v any, path ObjectPath, iface string) error {
|
||||||
|
return conn.ExportSubtreeWithMap(v, nil, path, iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportSubtreeWithMap works exactly like ExportSubtree but provides the
|
||||||
|
// ability to remap method names (e.g. export a lower-case method).
|
||||||
|
//
|
||||||
|
// The keys in the map are the real method names (exported on the struct), and
|
||||||
|
// the values are the method names to be exported on DBus.
|
||||||
|
func (conn *Conn) ExportSubtreeWithMap(v any, mapping map[string]string, path ObjectPath, iface string) error {
|
||||||
|
return conn.export(getMethods(v, mapping), path, iface, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportMethodTable like Export registers the given methods as an object
|
||||||
|
// on the message bus. Unlike Export the it uses a method table to define
|
||||||
|
// the object instead of a native go object.
|
||||||
|
//
|
||||||
|
// The method table is a map from method name to function closure
|
||||||
|
// representing the method. This allows an object exported on the bus to not
|
||||||
|
// necessarily be a native go object. It can be useful for generating exposed
|
||||||
|
// methods on the fly.
|
||||||
|
//
|
||||||
|
// Any non-function objects in the method table are ignored.
|
||||||
|
func (conn *Conn) ExportMethodTable(methods map[string]any, path ObjectPath, iface string) error {
|
||||||
|
return conn.exportMethodTable(methods, path, iface, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like ExportSubtree, but with the same caveats as ExportMethodTable.
|
||||||
|
func (conn *Conn) ExportSubtreeMethodTable(methods map[string]any, path ObjectPath, iface string) error {
|
||||||
|
return conn.exportMethodTable(methods, path, iface, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *Conn) exportMethodTable(methods map[string]any, path ObjectPath, iface string, includeSubtree bool) error {
|
||||||
|
var out map[string]reflect.Value
|
||||||
|
if methods != nil {
|
||||||
|
out = make(map[string]reflect.Value)
|
||||||
|
for name, method := range methods {
|
||||||
|
rval := reflect.ValueOf(method)
|
||||||
|
if rval.Kind() != reflect.Func {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t := rval.Type()
|
||||||
|
// only track valid methods must return *Error as last arg
|
||||||
|
if t.NumOut() == 0 ||
|
||||||
|
t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out[name] = rval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return conn.export(out, path, iface, includeSubtree)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *Conn) unexport(h *defaultHandler, path ObjectPath, iface string) error {
|
||||||
|
if h.PathExists(path) {
|
||||||
|
obj := h.objects[path]
|
||||||
|
obj.DeleteInterface(iface)
|
||||||
|
if len(obj.interfaces) == 0 {
|
||||||
|
h.DeleteObject(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// export is the worker function for all exports/registrations.
|
||||||
|
func (conn *Conn) export(methods map[string]reflect.Value, path ObjectPath, iface string, includeSubtree bool) error {
|
||||||
|
h, ok := conn.handler.(*defaultHandler)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf(
|
||||||
|
`dbus: export only allowed on the default handler. Received: %T"`,
|
||||||
|
conn.handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !path.IsValid() {
|
||||||
|
return fmt.Errorf(`dbus: Invalid path name: "%s"`, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a previous export if the interface is nil
|
||||||
|
if methods == nil {
|
||||||
|
return conn.unexport(h, path, iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is the first handler for this path, make a new map to hold all
|
||||||
|
// handlers for this path.
|
||||||
|
if !h.PathExists(path) {
|
||||||
|
h.AddObject(path, newExportedObject())
|
||||||
|
}
|
||||||
|
|
||||||
|
exportedMethods := make(map[string]Method)
|
||||||
|
for name, method := range methods {
|
||||||
|
exportedMethods[name] = exportedMethod{method}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, save this handler
|
||||||
|
obj := h.objects[path]
|
||||||
|
obj.AddInterface(iface, newExportedIntf(exportedMethods, includeSubtree))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseName calls org.freedesktop.DBus.ReleaseName and awaits a response.
|
||||||
|
func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) {
|
||||||
|
var r uint32
|
||||||
|
err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return ReleaseNameReply(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestName calls org.freedesktop.DBus.RequestName and awaits a response.
|
||||||
|
func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) {
|
||||||
|
var r uint32
|
||||||
|
err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return RequestNameReply(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseNameReply is the reply to a ReleaseName call.
|
||||||
|
type ReleaseNameReply uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
ReleaseNameReplyReleased ReleaseNameReply = 1 + iota
|
||||||
|
ReleaseNameReplyNonExistent
|
||||||
|
ReleaseNameReplyNotOwner
|
||||||
|
)
|
||||||
|
|
||||||
|
func (rep ReleaseNameReply) String() string {
|
||||||
|
switch rep {
|
||||||
|
case ReleaseNameReplyReleased:
|
||||||
|
return "released"
|
||||||
|
case ReleaseNameReplyNonExistent:
|
||||||
|
return "non existent"
|
||||||
|
case ReleaseNameReplyNotOwner:
|
||||||
|
return "not owner"
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestNameFlags represents the possible flags for a RequestName call.
|
||||||
|
type RequestNameFlags uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
NameFlagAllowReplacement RequestNameFlags = 1 << iota
|
||||||
|
NameFlagReplaceExisting
|
||||||
|
NameFlagDoNotQueue
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestNameReply is the reply to a RequestName call.
|
||||||
|
type RequestNameReply uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
RequestNameReplyPrimaryOwner RequestNameReply = 1 + iota
|
||||||
|
RequestNameReplyInQueue
|
||||||
|
RequestNameReplyExists
|
||||||
|
RequestNameReplyAlreadyOwner
|
||||||
|
)
|
||||||
|
|
||||||
|
func (rep RequestNameReply) String() string {
|
||||||
|
switch rep {
|
||||||
|
case RequestNameReplyPrimaryOwner:
|
||||||
|
return "primary owner"
|
||||||
|
case RequestNameReplyInQueue:
|
||||||
|
return "in queue"
|
||||||
|
case RequestNameReplyExists:
|
||||||
|
return "exists"
|
||||||
|
case RequestNameReplyAlreadyOwner:
|
||||||
|
return "already owner"
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
89
vendor/github.com/godbus/dbus/v5/match.go
generated
vendored
Normal file
89
vendor/github.com/godbus/dbus/v5/match.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MatchOption specifies option for dbus routing match rule. Options can be constructed with WithMatch* helpers.
|
||||||
|
// For full list of available options consult
|
||||||
|
// https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules
|
||||||
|
type MatchOption struct {
|
||||||
|
key string
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatMatchOptions(options []MatchOption) string {
|
||||||
|
items := make([]string, 0, len(options))
|
||||||
|
for _, option := range options {
|
||||||
|
items = append(items, option.key+"='"+option.value+"'")
|
||||||
|
}
|
||||||
|
return strings.Join(items, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMatchOption creates match option with given key and value
|
||||||
|
func WithMatchOption(key, value string) MatchOption {
|
||||||
|
return MatchOption{key, value}
|
||||||
|
}
|
||||||
|
|
||||||
|
// It does not make sense to have a public WithMatchType function
|
||||||
|
// because clients can only subscribe to messages with signal type.
|
||||||
|
func withMatchTypeSignal() MatchOption {
|
||||||
|
return WithMatchOption("type", "signal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMatchSender sets sender match option.
|
||||||
|
func WithMatchSender(sender string) MatchOption {
|
||||||
|
return WithMatchOption("sender", sender)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMatchSender sets interface match option.
|
||||||
|
func WithMatchInterface(iface string) MatchOption {
|
||||||
|
return WithMatchOption("interface", iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMatchMember sets member match option.
|
||||||
|
func WithMatchMember(member string) MatchOption {
|
||||||
|
return WithMatchOption("member", member)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMatchObjectPath creates match option that filters events based on given path
|
||||||
|
func WithMatchObjectPath(path ObjectPath) MatchOption {
|
||||||
|
return WithMatchOption("path", string(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMatchPathNamespace sets path_namespace match option.
|
||||||
|
func WithMatchPathNamespace(namespace ObjectPath) MatchOption {
|
||||||
|
return WithMatchOption("path_namespace", string(namespace))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMatchDestination sets destination match option.
|
||||||
|
func WithMatchDestination(destination string) MatchOption {
|
||||||
|
return WithMatchOption("destination", destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMatchArg sets argN match option, range of N is 0 to 63.
|
||||||
|
func WithMatchArg(argIdx int, value string) MatchOption {
|
||||||
|
if argIdx < 0 || argIdx > 63 {
|
||||||
|
panic("range of argument index is 0 to 63")
|
||||||
|
}
|
||||||
|
return WithMatchOption("arg"+strconv.Itoa(argIdx), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMatchArgPath sets argN path match option, range of N is 0 to 63.
|
||||||
|
func WithMatchArgPath(argIdx int, path string) MatchOption {
|
||||||
|
if argIdx < 0 || argIdx > 63 {
|
||||||
|
panic("range of argument index is 0 to 63")
|
||||||
|
}
|
||||||
|
return WithMatchOption("arg"+strconv.Itoa(argIdx)+"path", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMatchArg0Namespace sets arg0namespace match option.
|
||||||
|
func WithMatchArg0Namespace(arg0Namespace string) MatchOption {
|
||||||
|
return WithMatchOption("arg0namespace", arg0Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMatchEavesdrop sets eavesdrop match option.
|
||||||
|
func WithMatchEavesdrop(eavesdrop bool) MatchOption {
|
||||||
|
return WithMatchOption("eavesdrop", strconv.FormatBool(eavesdrop))
|
||||||
|
}
|
||||||
393
vendor/github.com/godbus/dbus/v5/message.go
generated
vendored
Normal file
393
vendor/github.com/godbus/dbus/v5/message.go
generated
vendored
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const protoVersion byte = 1
|
||||||
|
|
||||||
|
// Flags represents the possible flags of a D-Bus message.
|
||||||
|
type Flags byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
// FlagNoReplyExpected signals that the message is not expected to generate
|
||||||
|
// a reply. If this flag is set on outgoing messages, any possible reply
|
||||||
|
// will be discarded.
|
||||||
|
FlagNoReplyExpected Flags = 1 << iota
|
||||||
|
// FlagNoAutoStart signals that the message bus should not automatically
|
||||||
|
// start an application when handling this message.
|
||||||
|
FlagNoAutoStart
|
||||||
|
// FlagAllowInteractiveAuthorization may be set on a method call
|
||||||
|
// message to inform the receiving side that the caller is prepared
|
||||||
|
// to wait for interactive authorization, which might take a
|
||||||
|
// considerable time to complete. For instance, if this flag is set,
|
||||||
|
// it would be appropriate to query the user for passwords or
|
||||||
|
// confirmation via Polkit or a similar framework.
|
||||||
|
FlagAllowInteractiveAuthorization
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type represents the possible types of a D-Bus message.
|
||||||
|
type Type byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeMethodCall Type = 1 + iota
|
||||||
|
TypeMethodReply
|
||||||
|
TypeError
|
||||||
|
TypeSignal
|
||||||
|
typeMax
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t Type) String() string {
|
||||||
|
switch t {
|
||||||
|
case TypeMethodCall:
|
||||||
|
return "method call"
|
||||||
|
case TypeMethodReply:
|
||||||
|
return "reply"
|
||||||
|
case TypeError:
|
||||||
|
return "error"
|
||||||
|
case TypeSignal:
|
||||||
|
return "signal"
|
||||||
|
}
|
||||||
|
return "invalid"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderField represents the possible byte codes for the headers
|
||||||
|
// of a D-Bus message.
|
||||||
|
type HeaderField byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
FieldPath HeaderField = 1 + iota
|
||||||
|
FieldInterface
|
||||||
|
FieldMember
|
||||||
|
FieldErrorName
|
||||||
|
FieldReplySerial
|
||||||
|
FieldDestination
|
||||||
|
FieldSender
|
||||||
|
FieldSignature
|
||||||
|
FieldUnixFDs
|
||||||
|
fieldMax
|
||||||
|
)
|
||||||
|
|
||||||
|
// An InvalidMessageError describes the reason why a D-Bus message is regarded as
|
||||||
|
// invalid.
|
||||||
|
type InvalidMessageError string
|
||||||
|
|
||||||
|
func (e InvalidMessageError) Error() string {
|
||||||
|
return "dbus: invalid message: " + string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldType are the types of the various header fields.
|
||||||
|
var fieldTypes = [fieldMax]reflect.Type{
|
||||||
|
FieldPath: objectPathType,
|
||||||
|
FieldInterface: stringType,
|
||||||
|
FieldMember: stringType,
|
||||||
|
FieldErrorName: stringType,
|
||||||
|
FieldReplySerial: uint32Type,
|
||||||
|
FieldDestination: stringType,
|
||||||
|
FieldSender: stringType,
|
||||||
|
FieldSignature: signatureType,
|
||||||
|
FieldUnixFDs: uint32Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
// requiredFields lists the header fields that are required by the different
|
||||||
|
// message types.
|
||||||
|
var requiredFields = [typeMax][]HeaderField{
|
||||||
|
TypeMethodCall: {FieldPath, FieldMember},
|
||||||
|
TypeMethodReply: {FieldReplySerial},
|
||||||
|
TypeError: {FieldErrorName, FieldReplySerial},
|
||||||
|
TypeSignal: {FieldPath, FieldInterface, FieldMember},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message represents a single D-Bus message.
|
||||||
|
type Message struct {
|
||||||
|
Type
|
||||||
|
Flags
|
||||||
|
Headers map[HeaderField]Variant
|
||||||
|
Body []any
|
||||||
|
|
||||||
|
serial uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type header struct {
|
||||||
|
Field byte
|
||||||
|
Variant
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeMessageWithFDs(rd io.Reader, fds []int) (msg *Message, err error) {
|
||||||
|
var order binary.ByteOrder
|
||||||
|
var hlength, length uint32
|
||||||
|
var typ, flags, proto byte
|
||||||
|
var headers []header
|
||||||
|
|
||||||
|
b := make([]byte, 1)
|
||||||
|
_, err = rd.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch b[0] {
|
||||||
|
case 'l':
|
||||||
|
order = binary.LittleEndian
|
||||||
|
case 'B':
|
||||||
|
order = binary.BigEndian
|
||||||
|
default:
|
||||||
|
return nil, InvalidMessageError("invalid byte order")
|
||||||
|
}
|
||||||
|
|
||||||
|
dec := newDecoder(rd, order, fds)
|
||||||
|
dec.pos = 1
|
||||||
|
|
||||||
|
msg = new(Message)
|
||||||
|
vs, err := dec.Decode(Signature{"yyyuu"})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = Store(vs, &typ, &flags, &proto, &length, &msg.serial); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
msg.Type = Type(typ)
|
||||||
|
msg.Flags = Flags(flags)
|
||||||
|
|
||||||
|
// get the header length separately because we need it later
|
||||||
|
b = make([]byte, 4)
|
||||||
|
_, err = io.ReadFull(rd, b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Read(bytes.NewBuffer(b), order, &hlength); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if hlength+length+16 > 1<<27 {
|
||||||
|
return nil, InvalidMessageError("message is too long")
|
||||||
|
}
|
||||||
|
dec = newDecoder(io.MultiReader(bytes.NewBuffer(b), rd), order, fds)
|
||||||
|
dec.pos = 12
|
||||||
|
vs, err = dec.Decode(Signature{"a(yv)"})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = Store(vs, &headers); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Headers = make(map[HeaderField]Variant)
|
||||||
|
for _, v := range headers {
|
||||||
|
msg.Headers[HeaderField(v.Field)] = v.Variant
|
||||||
|
}
|
||||||
|
|
||||||
|
dec.align(8)
|
||||||
|
body := make([]byte, int(length))
|
||||||
|
if length != 0 {
|
||||||
|
_, err := io.ReadFull(rd, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = msg.validateHeader(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sig, _ := msg.Headers[FieldSignature].value.(Signature)
|
||||||
|
if sig.str != "" {
|
||||||
|
buf := bytes.NewBuffer(body)
|
||||||
|
dec = newDecoder(buf, order, fds)
|
||||||
|
vs, err := dec.Decode(sig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
msg.Body = vs
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMessage tries to decode a single message in the D-Bus wire format
|
||||||
|
// from the given reader. The byte order is figured out from the first byte.
|
||||||
|
// The possibly returned error can be an error of the underlying reader, an
|
||||||
|
// InvalidMessageError or a FormatError.
|
||||||
|
func DecodeMessage(rd io.Reader) (msg *Message, err error) {
|
||||||
|
return DecodeMessageWithFDs(rd, make([]int, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
type nullwriter struct{}
|
||||||
|
|
||||||
|
func (nullwriter) Write(p []byte) (cnt int, err error) {
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *Message) CountFds() (int, error) {
|
||||||
|
if len(msg.Body) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
enc := newEncoder(nullwriter{}, nativeEndian, make([]int, 0))
|
||||||
|
err := enc.Encode(msg.Body...)
|
||||||
|
return len(enc.fds), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *Message) EncodeToWithFDs(out io.Writer, order binary.ByteOrder) (fds []int, err error) {
|
||||||
|
if err := msg.validateHeader(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var vs [7]any
|
||||||
|
switch order {
|
||||||
|
case binary.LittleEndian:
|
||||||
|
vs[0] = byte('l')
|
||||||
|
case binary.BigEndian:
|
||||||
|
vs[0] = byte('B')
|
||||||
|
default:
|
||||||
|
return nil, errors.New("dbus: invalid byte order")
|
||||||
|
}
|
||||||
|
body := new(bytes.Buffer)
|
||||||
|
fds = make([]int, 0)
|
||||||
|
enc := newEncoder(body, order, fds)
|
||||||
|
if len(msg.Body) != 0 {
|
||||||
|
err = enc.Encode(msg.Body...)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vs[1] = msg.Type
|
||||||
|
vs[2] = msg.Flags
|
||||||
|
vs[3] = protoVersion
|
||||||
|
vs[4] = uint32(len(body.Bytes()))
|
||||||
|
vs[5] = msg.serial
|
||||||
|
headers := make([]header, 0, len(msg.Headers))
|
||||||
|
for k, v := range msg.Headers {
|
||||||
|
headers = append(headers, header{byte(k), v})
|
||||||
|
}
|
||||||
|
vs[6] = headers
|
||||||
|
var buf bytes.Buffer
|
||||||
|
enc = newEncoder(&buf, order, enc.fds)
|
||||||
|
err = enc.Encode(vs[:]...)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
enc.align(8)
|
||||||
|
if _, err := body.WriteTo(&buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if buf.Len() > 1<<27 {
|
||||||
|
return nil, InvalidMessageError("message is too long")
|
||||||
|
}
|
||||||
|
if _, err := buf.WriteTo(out); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return enc.fds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeTo encodes and sends a message to the given writer. The byte order must
|
||||||
|
// be either binary.LittleEndian or binary.BigEndian. If the message is not
|
||||||
|
// valid or an error occurs when writing, an error is returned.
|
||||||
|
func (msg *Message) EncodeTo(out io.Writer, order binary.ByteOrder) (err error) {
|
||||||
|
_, err = msg.EncodeToWithFDs(out, order)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid checks whether msg is a valid message and returns an
|
||||||
|
// InvalidMessageError or FormatError if it is not.
|
||||||
|
func (msg *Message) IsValid() error {
|
||||||
|
return msg.EncodeTo(io.Discard, nativeEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *Message) validateHeader() error {
|
||||||
|
if msg.Flags & ^(FlagNoAutoStart|FlagNoReplyExpected|FlagAllowInteractiveAuthorization) != 0 {
|
||||||
|
return InvalidMessageError("invalid flags")
|
||||||
|
}
|
||||||
|
if msg.Type == 0 || msg.Type >= typeMax {
|
||||||
|
return InvalidMessageError("invalid message type")
|
||||||
|
}
|
||||||
|
for k, v := range msg.Headers {
|
||||||
|
if k == 0 || k >= fieldMax {
|
||||||
|
return InvalidMessageError("invalid header")
|
||||||
|
}
|
||||||
|
if reflect.TypeOf(v.value) != fieldTypes[k] {
|
||||||
|
return InvalidMessageError("invalid type of header field")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, v := range requiredFields[msg.Type] {
|
||||||
|
if _, ok := msg.Headers[v]; !ok {
|
||||||
|
return InvalidMessageError("missing required header")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if path, ok := msg.Headers[FieldPath]; ok {
|
||||||
|
if !path.value.(ObjectPath).IsValid() {
|
||||||
|
return InvalidMessageError("invalid path name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if iface, ok := msg.Headers[FieldInterface]; ok {
|
||||||
|
if !isValidInterface(iface.value.(string)) {
|
||||||
|
return InvalidMessageError("invalid interface name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if member, ok := msg.Headers[FieldMember]; ok {
|
||||||
|
if !isValidMember(member.value.(string)) {
|
||||||
|
return InvalidMessageError("invalid member name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errname, ok := msg.Headers[FieldErrorName]; ok {
|
||||||
|
if !isValidInterface(errname.value.(string)) {
|
||||||
|
return InvalidMessageError("invalid error name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(msg.Body) != 0 {
|
||||||
|
if _, ok := msg.Headers[FieldSignature]; !ok {
|
||||||
|
return InvalidMessageError("missing signature")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serial returns the message's serial number. The returned value is only valid
|
||||||
|
// for messages received by eavesdropping.
|
||||||
|
func (msg *Message) Serial() uint32 {
|
||||||
|
return msg.serial
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of a message similar to the format of
|
||||||
|
// dbus-monitor.
|
||||||
|
func (msg *Message) String() string {
|
||||||
|
if err := msg.IsValid(); err != nil {
|
||||||
|
return "<invalid>"
|
||||||
|
}
|
||||||
|
s := msg.Type.String()
|
||||||
|
if v, ok := msg.Headers[FieldSender]; ok {
|
||||||
|
s += " from " + v.value.(string)
|
||||||
|
}
|
||||||
|
if v, ok := msg.Headers[FieldDestination]; ok {
|
||||||
|
s += " to " + v.value.(string)
|
||||||
|
}
|
||||||
|
s += " serial " + strconv.FormatUint(uint64(msg.serial), 10)
|
||||||
|
if v, ok := msg.Headers[FieldReplySerial]; ok {
|
||||||
|
s += " reply_serial " + strconv.FormatUint(uint64(v.value.(uint32)), 10)
|
||||||
|
}
|
||||||
|
if v, ok := msg.Headers[FieldUnixFDs]; ok {
|
||||||
|
s += " unixfds " + strconv.FormatUint(uint64(v.value.(uint32)), 10)
|
||||||
|
}
|
||||||
|
if v, ok := msg.Headers[FieldPath]; ok {
|
||||||
|
s += " path " + string(v.value.(ObjectPath))
|
||||||
|
}
|
||||||
|
if v, ok := msg.Headers[FieldInterface]; ok {
|
||||||
|
s += " interface " + v.value.(string)
|
||||||
|
}
|
||||||
|
if v, ok := msg.Headers[FieldErrorName]; ok {
|
||||||
|
s += " error " + v.value.(string)
|
||||||
|
}
|
||||||
|
if v, ok := msg.Headers[FieldMember]; ok {
|
||||||
|
s += " member " + v.value.(string)
|
||||||
|
}
|
||||||
|
if len(msg.Body) != 0 {
|
||||||
|
s += "\n"
|
||||||
|
}
|
||||||
|
for i, v := range msg.Body {
|
||||||
|
s += " " + MakeVariant(v).String()
|
||||||
|
if i != len(msg.Body)-1 {
|
||||||
|
s += "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
181
vendor/github.com/godbus/dbus/v5/object.go
generated
vendored
Normal file
181
vendor/github.com/godbus/dbus/v5/object.go
generated
vendored
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BusObject is the interface of a remote object on which methods can be
|
||||||
|
// invoked.
|
||||||
|
type BusObject interface {
|
||||||
|
Call(method string, flags Flags, args ...any) *Call
|
||||||
|
CallWithContext(ctx context.Context, method string, flags Flags, args ...any) *Call
|
||||||
|
Go(method string, flags Flags, ch chan *Call, args ...any) *Call
|
||||||
|
GoWithContext(ctx context.Context, method string, flags Flags, ch chan *Call, args ...any) *Call
|
||||||
|
AddMatchSignal(iface, member string, options ...MatchOption) *Call
|
||||||
|
RemoveMatchSignal(iface, member string, options ...MatchOption) *Call
|
||||||
|
GetProperty(p string) (Variant, error)
|
||||||
|
StoreProperty(p string, value any) error
|
||||||
|
SetProperty(p string, v any) error
|
||||||
|
Destination() string
|
||||||
|
Path() ObjectPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object represents a remote object on which methods can be invoked.
|
||||||
|
type Object struct {
|
||||||
|
conn *Conn
|
||||||
|
dest string
|
||||||
|
path ObjectPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call calls a method with (*Object).Go and waits for its reply.
|
||||||
|
func (o *Object) Call(method string, flags Flags, args ...any) *Call {
|
||||||
|
return <-o.createCall(context.Background(), method, flags, make(chan *Call, 1), args...).Done
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallWithContext acts like Call but takes a context
|
||||||
|
func (o *Object) CallWithContext(ctx context.Context, method string, flags Flags, args ...any) *Call {
|
||||||
|
return <-o.createCall(ctx, method, flags, make(chan *Call, 1), args...).Done
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddMatchSignal subscribes BusObject to signals from specified interface,
|
||||||
|
// method (member). Additional filter rules can be added via WithMatch* option constructors.
|
||||||
|
// Note: To filter events by object path you have to specify this path via an option.
|
||||||
|
//
|
||||||
|
// Deprecated: use (*Conn) AddMatchSignal instead.
|
||||||
|
func (o *Object) AddMatchSignal(iface, member string, options ...MatchOption) *Call {
|
||||||
|
base := []MatchOption{
|
||||||
|
withMatchTypeSignal(),
|
||||||
|
WithMatchInterface(iface),
|
||||||
|
WithMatchMember(member),
|
||||||
|
}
|
||||||
|
|
||||||
|
options = append(base, options...)
|
||||||
|
return o.conn.BusObject().Call(
|
||||||
|
"org.freedesktop.DBus.AddMatch",
|
||||||
|
0,
|
||||||
|
formatMatchOptions(options),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveMatchSignal unsubscribes BusObject from signals from specified interface,
|
||||||
|
// method (member). Additional filter rules can be added via WithMatch* option constructors
|
||||||
|
//
|
||||||
|
// Deprecated: use (*Conn) RemoveMatchSignal instead.
|
||||||
|
func (o *Object) RemoveMatchSignal(iface, member string, options ...MatchOption) *Call {
|
||||||
|
base := []MatchOption{
|
||||||
|
withMatchTypeSignal(),
|
||||||
|
WithMatchInterface(iface),
|
||||||
|
WithMatchMember(member),
|
||||||
|
}
|
||||||
|
|
||||||
|
options = append(base, options...)
|
||||||
|
return o.conn.BusObject().Call(
|
||||||
|
"org.freedesktop.DBus.RemoveMatch",
|
||||||
|
0,
|
||||||
|
formatMatchOptions(options),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go calls a method with the given arguments asynchronously. It returns a
|
||||||
|
// Call structure representing this method call. The passed channel will
|
||||||
|
// return the same value once the call is done. If ch is nil, a new channel
|
||||||
|
// will be allocated. Otherwise, ch has to be buffered or Go will panic.
|
||||||
|
//
|
||||||
|
// If the flags include FlagNoReplyExpected, ch is ignored and a Call structure
|
||||||
|
// is returned with any error in Err and a closed channel in Done containing
|
||||||
|
// the returned Call as it's one entry.
|
||||||
|
//
|
||||||
|
// If the method parameter contains a dot ('.'), the part before the last dot
|
||||||
|
// specifies the interface on which the method is called.
|
||||||
|
func (o *Object) Go(method string, flags Flags, ch chan *Call, args ...any) *Call {
|
||||||
|
return o.createCall(context.Background(), method, flags, ch, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoWithContext acts like Go but takes a context
|
||||||
|
func (o *Object) GoWithContext(ctx context.Context, method string, flags Flags, ch chan *Call, args ...any) *Call {
|
||||||
|
return o.createCall(ctx, method, flags, ch, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Object) createCall(ctx context.Context, method string, flags Flags, ch chan *Call, args ...any) *Call {
|
||||||
|
if ctx == nil {
|
||||||
|
panic("nil context")
|
||||||
|
}
|
||||||
|
iface := ""
|
||||||
|
i := strings.LastIndex(method, ".")
|
||||||
|
if i != -1 {
|
||||||
|
iface = method[:i]
|
||||||
|
}
|
||||||
|
method = method[i+1:]
|
||||||
|
msg := new(Message)
|
||||||
|
msg.Type = TypeMethodCall
|
||||||
|
msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected)
|
||||||
|
msg.Headers = make(map[HeaderField]Variant)
|
||||||
|
msg.Headers[FieldPath] = MakeVariant(o.path)
|
||||||
|
msg.Headers[FieldDestination] = MakeVariant(o.dest)
|
||||||
|
msg.Headers[FieldMember] = MakeVariant(method)
|
||||||
|
if iface != "" {
|
||||||
|
msg.Headers[FieldInterface] = MakeVariant(iface)
|
||||||
|
}
|
||||||
|
msg.Body = args
|
||||||
|
if len(args) > 0 {
|
||||||
|
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(args...))
|
||||||
|
}
|
||||||
|
return o.conn.SendWithContext(ctx, msg, ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProperty calls org.freedesktop.DBus.Properties.Get on the given
|
||||||
|
// object. The property name must be given in interface.member notation.
|
||||||
|
func (o *Object) GetProperty(p string) (Variant, error) {
|
||||||
|
var result Variant
|
||||||
|
err := o.StoreProperty(p, &result)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreProperty calls org.freedesktop.DBus.Properties.Get on the given
|
||||||
|
// object. The property name must be given in interface.member notation.
|
||||||
|
// It stores the returned property into the provided value.
|
||||||
|
func (o *Object) StoreProperty(p string, value any) error {
|
||||||
|
idx := strings.LastIndex(p, ".")
|
||||||
|
if idx == -1 || idx+1 == len(p) {
|
||||||
|
return errors.New("dbus: invalid property " + p)
|
||||||
|
}
|
||||||
|
|
||||||
|
iface := p[:idx]
|
||||||
|
prop := p[idx+1:]
|
||||||
|
|
||||||
|
return o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop).
|
||||||
|
Store(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProperty calls org.freedesktop.DBus.Properties.Set on the given
|
||||||
|
// object. The property name must be given in interface.member notation.
|
||||||
|
// Panics if v is not a valid Variant type.
|
||||||
|
func (o *Object) SetProperty(p string, v any) error {
|
||||||
|
// v might already be a variant...
|
||||||
|
variant, ok := v.(Variant)
|
||||||
|
if !ok {
|
||||||
|
// Otherwise, make it into one.
|
||||||
|
variant = MakeVariant(v)
|
||||||
|
}
|
||||||
|
idx := strings.LastIndex(p, ".")
|
||||||
|
if idx == -1 || idx+1 == len(p) {
|
||||||
|
return errors.New("dbus: invalid property " + p)
|
||||||
|
}
|
||||||
|
|
||||||
|
iface := p[:idx]
|
||||||
|
prop := p[idx+1:]
|
||||||
|
|
||||||
|
return o.Call("org.freedesktop.DBus.Properties.Set", 0, iface, prop, variant).Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destination returns the destination that calls on (o *Object) are sent to.
|
||||||
|
func (o *Object) Destination() string {
|
||||||
|
return o.dest
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path returns the path that calls on (o *Object") are sent to.
|
||||||
|
func (o *Object) Path() ObjectPath {
|
||||||
|
return o.path
|
||||||
|
}
|
||||||
24
vendor/github.com/godbus/dbus/v5/sequence.go
generated
vendored
Normal file
24
vendor/github.com/godbus/dbus/v5/sequence.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
// Sequence represents the value of a monotonically increasing counter.
|
||||||
|
type Sequence uint64
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NoSequence indicates the absence of a sequence value.
|
||||||
|
NoSequence Sequence = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// sequenceGenerator represents a monotonically increasing counter.
|
||||||
|
type sequenceGenerator struct {
|
||||||
|
nextSequence Sequence
|
||||||
|
}
|
||||||
|
|
||||||
|
func (generator *sequenceGenerator) next() Sequence {
|
||||||
|
result := generator.nextSequence
|
||||||
|
generator.nextSequence++
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSequenceGenerator() *sequenceGenerator {
|
||||||
|
return &sequenceGenerator{nextSequence: 1}
|
||||||
|
}
|
||||||
125
vendor/github.com/godbus/dbus/v5/sequential_handler.go
generated
vendored
Normal file
125
vendor/github.com/godbus/dbus/v5/sequential_handler.go
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewSequentialSignalHandler returns an instance of a new
|
||||||
|
// signal handler that guarantees sequential processing of signals. It is a
|
||||||
|
// guarantee of this signal handler that signals will be written to
|
||||||
|
// channels in the order they are received on the DBus connection.
|
||||||
|
func NewSequentialSignalHandler() SignalHandler {
|
||||||
|
return &sequentialSignalHandler{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type sequentialSignalHandler struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
closed bool
|
||||||
|
signals []*sequentialSignalChannelData
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh *sequentialSignalHandler) DeliverSignal(intf, name string, signal *Signal) {
|
||||||
|
sh.mu.RLock()
|
||||||
|
defer sh.mu.RUnlock()
|
||||||
|
if sh.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, scd := range sh.signals {
|
||||||
|
scd.deliver(signal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh *sequentialSignalHandler) Terminate() {
|
||||||
|
sh.mu.Lock()
|
||||||
|
defer sh.mu.Unlock()
|
||||||
|
if sh.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, scd := range sh.signals {
|
||||||
|
scd.close()
|
||||||
|
close(scd.ch)
|
||||||
|
}
|
||||||
|
sh.closed = true
|
||||||
|
sh.signals = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh *sequentialSignalHandler) AddSignal(ch chan<- *Signal) {
|
||||||
|
sh.mu.Lock()
|
||||||
|
defer sh.mu.Unlock()
|
||||||
|
if sh.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sh.signals = append(sh.signals, newSequentialSignalChannelData(ch))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh *sequentialSignalHandler) RemoveSignal(ch chan<- *Signal) {
|
||||||
|
sh.mu.Lock()
|
||||||
|
defer sh.mu.Unlock()
|
||||||
|
if sh.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := len(sh.signals) - 1; i >= 0; i-- {
|
||||||
|
if ch == sh.signals[i].ch {
|
||||||
|
sh.signals[i].close()
|
||||||
|
copy(sh.signals[i:], sh.signals[i+1:])
|
||||||
|
sh.signals[len(sh.signals)-1] = nil
|
||||||
|
sh.signals = sh.signals[:len(sh.signals)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type sequentialSignalChannelData struct {
|
||||||
|
ch chan<- *Signal
|
||||||
|
in chan *Signal
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSequentialSignalChannelData(ch chan<- *Signal) *sequentialSignalChannelData {
|
||||||
|
scd := &sequentialSignalChannelData{
|
||||||
|
ch: ch,
|
||||||
|
in: make(chan *Signal),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
go scd.bufferSignals()
|
||||||
|
return scd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scd *sequentialSignalChannelData) bufferSignals() {
|
||||||
|
defer close(scd.done)
|
||||||
|
|
||||||
|
// Ensure that signals are delivered to scd.ch in the same
|
||||||
|
// order they are received from scd.in.
|
||||||
|
var queue []*Signal
|
||||||
|
for {
|
||||||
|
if len(queue) == 0 {
|
||||||
|
signal, ok := <-scd.in
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
queue = append(queue, signal)
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case scd.ch <- queue[0]:
|
||||||
|
copy(queue, queue[1:])
|
||||||
|
queue[len(queue)-1] = nil
|
||||||
|
queue = queue[:len(queue)-1]
|
||||||
|
case signal, ok := <-scd.in:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
queue = append(queue, signal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scd *sequentialSignalChannelData) deliver(signal *Signal) {
|
||||||
|
scd.in <- signal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scd *sequentialSignalChannelData) close() {
|
||||||
|
close(scd.in)
|
||||||
|
// Ensure that bufferSignals() has exited and won't attempt
|
||||||
|
// any future sends on scd.ch
|
||||||
|
<-scd.done
|
||||||
|
}
|
||||||
107
vendor/github.com/godbus/dbus/v5/server_interfaces.go
generated
vendored
Normal file
107
vendor/github.com/godbus/dbus/v5/server_interfaces.go
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
// Terminator allows a handler to implement a shutdown mechanism that
|
||||||
|
// is called when the connection terminates.
|
||||||
|
type Terminator interface {
|
||||||
|
Terminate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler is the representation of a D-Bus Application.
|
||||||
|
//
|
||||||
|
// The Handler must have a way to lookup objects given
|
||||||
|
// an ObjectPath. The returned object must implement the
|
||||||
|
// ServerObject interface.
|
||||||
|
type Handler interface {
|
||||||
|
LookupObject(path ObjectPath) (ServerObject, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerObject is the representation of an D-Bus Object.
|
||||||
|
//
|
||||||
|
// Objects are registered at a path for a given Handler.
|
||||||
|
// The Objects implement D-Bus interfaces. The semantics
|
||||||
|
// of Interface lookup is up to the implementation of
|
||||||
|
// the ServerObject. The ServerObject implementation may
|
||||||
|
// choose to implement empty string as a valid interface
|
||||||
|
// representing all methods or not per the D-Bus specification.
|
||||||
|
type ServerObject interface {
|
||||||
|
LookupInterface(name string) (Interface, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An Interface is the representation of a D-Bus Interface.
|
||||||
|
//
|
||||||
|
// Interfaces are a grouping of methods implemented by the Objects.
|
||||||
|
// Interfaces are responsible for routing method calls.
|
||||||
|
type Interface interface {
|
||||||
|
LookupMethod(name string) (Method, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Method represents the exposed methods on D-Bus.
|
||||||
|
type Method interface {
|
||||||
|
// Call requires that all arguments are decoded before being passed to it.
|
||||||
|
Call(args ...any) ([]any, error)
|
||||||
|
NumArguments() int
|
||||||
|
NumReturns() int
|
||||||
|
// ArgumentValue returns a representative value for the argument at position
|
||||||
|
// it should be of the proper type. reflect.Zero would be a good mechanism
|
||||||
|
// to use for this Value.
|
||||||
|
ArgumentValue(position int) any
|
||||||
|
// ReturnValue returns a representative value for the return at position
|
||||||
|
// it should be of the proper type. reflect.Zero would be a good mechanism
|
||||||
|
// to use for this Value.
|
||||||
|
ReturnValue(position int) any
|
||||||
|
}
|
||||||
|
|
||||||
|
// An Argument Decoder can decode arguments using the non-standard mechanism
|
||||||
|
//
|
||||||
|
// If a method implements this interface then the non-standard
|
||||||
|
// decoder will be used.
|
||||||
|
//
|
||||||
|
// Method arguments must be decoded from the message.
|
||||||
|
// The mechanism for doing this will vary based on the
|
||||||
|
// implementation of the method. A normal approach is provided
|
||||||
|
// as part of this library, but may be replaced with
|
||||||
|
// any other decoding scheme.
|
||||||
|
type ArgumentDecoder interface {
|
||||||
|
// To decode the arguments of a method the sender and message are
|
||||||
|
// provided in case the semantics of the implementer provides access
|
||||||
|
// to these as part of the method invocation.
|
||||||
|
DecodeArguments(conn *Conn, sender string, msg *Message, args []any) ([]any, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A SignalHandler is responsible for delivering a signal.
|
||||||
|
//
|
||||||
|
// Signal delivery may be changed from the default channel
|
||||||
|
// based approach by Handlers implementing the SignalHandler
|
||||||
|
// interface.
|
||||||
|
type SignalHandler interface {
|
||||||
|
DeliverSignal(iface, name string, signal *Signal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignalRegistrar manages signal delivery channels.
|
||||||
|
//
|
||||||
|
// This is an optional set of methods for `SignalHandler`.
|
||||||
|
type SignalRegistrar interface {
|
||||||
|
AddSignal(ch chan<- *Signal)
|
||||||
|
RemoveSignal(ch chan<- *Signal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A DBusError is used to convert a generic object to a D-Bus error.
|
||||||
|
//
|
||||||
|
// Any custom error mechanism may implement this interface to provide
|
||||||
|
// a custom encoding of the error on D-Bus. By default if a normal
|
||||||
|
// error is returned, it will be encoded as the generic
|
||||||
|
// "org.freedesktop.DBus.Error.Failed" error. By implementing this
|
||||||
|
// interface as well a custom encoding may be provided.
|
||||||
|
type DBusError interface {
|
||||||
|
DBusError() (string, []any)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SerialGenerator is responsible for serials generation.
|
||||||
|
//
|
||||||
|
// Different approaches for the serial generation can be used,
|
||||||
|
// maintaining a map guarded with a mutex (the standard way) or
|
||||||
|
// simply increment an atomic counter.
|
||||||
|
type SerialGenerator interface {
|
||||||
|
GetSerial() uint32
|
||||||
|
RetireSerial(serial uint32)
|
||||||
|
}
|
||||||
298
vendor/github.com/godbus/dbus/v5/sig.go
generated
vendored
Normal file
298
vendor/github.com/godbus/dbus/v5/sig.go
generated
vendored
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sigToType = map[byte]reflect.Type{
|
||||||
|
'y': byteType,
|
||||||
|
'b': boolType,
|
||||||
|
'n': int16Type,
|
||||||
|
'q': uint16Type,
|
||||||
|
'i': int32Type,
|
||||||
|
'u': uint32Type,
|
||||||
|
'x': int64Type,
|
||||||
|
't': uint64Type,
|
||||||
|
'd': float64Type,
|
||||||
|
's': stringType,
|
||||||
|
'g': signatureType,
|
||||||
|
'o': objectPathType,
|
||||||
|
'v': variantType,
|
||||||
|
'h': unixFDIndexType,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signature represents a correct type signature as specified by the D-Bus
|
||||||
|
// specification. The zero value represents the empty signature, "".
|
||||||
|
type Signature struct {
|
||||||
|
str string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignatureOf returns the concatenation of all the signatures of the given
|
||||||
|
// values. It panics if one of them is not representable in D-Bus.
|
||||||
|
func SignatureOf(vs ...any) Signature {
|
||||||
|
var s string
|
||||||
|
for _, v := range vs {
|
||||||
|
s += getSignature(reflect.TypeOf(v), &depthCounter{})
|
||||||
|
}
|
||||||
|
return Signature{s}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignatureOfType returns the signature of the given type. It panics if the
|
||||||
|
// type is not representable in D-Bus.
|
||||||
|
func SignatureOfType(t reflect.Type) Signature {
|
||||||
|
return Signature{getSignature(t, &depthCounter{})}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSignature returns the signature of the given type and panics on unknown types.
|
||||||
|
func getSignature(t reflect.Type, depth *depthCounter) (sig string) {
|
||||||
|
if !depth.Valid() {
|
||||||
|
panic("container nesting too deep")
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if len(sig) > 255 {
|
||||||
|
panic("signature exceeds the length limitation")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// handle simple types first
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Uint8:
|
||||||
|
return "y"
|
||||||
|
case reflect.Bool:
|
||||||
|
return "b"
|
||||||
|
case reflect.Int16:
|
||||||
|
return "n"
|
||||||
|
case reflect.Uint16:
|
||||||
|
return "q"
|
||||||
|
case reflect.Int, reflect.Int32:
|
||||||
|
if t == unixFDType {
|
||||||
|
return "h"
|
||||||
|
}
|
||||||
|
return "i"
|
||||||
|
case reflect.Uint, reflect.Uint32:
|
||||||
|
if t == unixFDIndexType {
|
||||||
|
return "h"
|
||||||
|
}
|
||||||
|
return "u"
|
||||||
|
case reflect.Int64:
|
||||||
|
return "x"
|
||||||
|
case reflect.Uint64:
|
||||||
|
return "t"
|
||||||
|
case reflect.Float64:
|
||||||
|
return "d"
|
||||||
|
case reflect.Ptr:
|
||||||
|
return getSignature(t.Elem(), depth)
|
||||||
|
case reflect.String:
|
||||||
|
if t == objectPathType {
|
||||||
|
return "o"
|
||||||
|
}
|
||||||
|
return "s"
|
||||||
|
case reflect.Struct:
|
||||||
|
switch t {
|
||||||
|
case variantType:
|
||||||
|
return "v"
|
||||||
|
case signatureType:
|
||||||
|
return "g"
|
||||||
|
}
|
||||||
|
var s string
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
|
||||||
|
s += getSignature(t.Field(i).Type, depth.EnterStruct())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(s) == 0 {
|
||||||
|
panic(InvalidTypeError{t})
|
||||||
|
}
|
||||||
|
return "(" + s + ")"
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
return "a" + getSignature(t.Elem(), depth.EnterArray())
|
||||||
|
case reflect.Map:
|
||||||
|
if !isKeyType(t.Key()) {
|
||||||
|
panic(InvalidTypeError{t})
|
||||||
|
}
|
||||||
|
return "a{" + getSignature(t.Key(), depth.EnterArray().EnterDictEntry()) + getSignature(t.Elem(), depth.EnterArray().EnterDictEntry()) + "}"
|
||||||
|
case reflect.Interface:
|
||||||
|
return "v"
|
||||||
|
}
|
||||||
|
panic(InvalidTypeError{t})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSignature returns the signature represented by this string, or a
|
||||||
|
// SignatureError if the string is not a valid signature.
|
||||||
|
func ParseSignature(s string) (sig Signature, err error) {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(s) > 255 {
|
||||||
|
return Signature{""}, SignatureError{s, "too long"}
|
||||||
|
}
|
||||||
|
sig.str = s
|
||||||
|
for err == nil && len(s) != 0 {
|
||||||
|
err, s = validSingle(s, &depthCounter{})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
sig = Signature{""}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSignatureMust behaves like ParseSignature, except that it panics if s
|
||||||
|
// is not valid.
|
||||||
|
func ParseSignatureMust(s string) Signature {
|
||||||
|
sig, err := ParseSignature(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return sig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty returns whether the signature is the empty signature.
|
||||||
|
func (s Signature) Empty() bool {
|
||||||
|
return s.str == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single returns whether the signature represents a single, complete type.
|
||||||
|
func (s Signature) Single() bool {
|
||||||
|
err, r := validSingle(s.str, &depthCounter{})
|
||||||
|
return err != nil && r == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the signature's string representation.
|
||||||
|
func (s Signature) String() string {
|
||||||
|
return s.str
|
||||||
|
}
|
||||||
|
|
||||||
|
// A SignatureError indicates that a signature passed to a function or received
|
||||||
|
// on a connection is not a valid signature.
|
||||||
|
type SignatureError struct {
|
||||||
|
Sig string
|
||||||
|
Reason string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e SignatureError) Error() string {
|
||||||
|
return fmt.Sprintf("dbus: invalid signature: %q (%s)", e.Sig, e.Reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
type depthCounter struct {
|
||||||
|
arrayDepth, structDepth, dictEntryDepth int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cnt *depthCounter) Valid() bool {
|
||||||
|
return cnt.arrayDepth <= 32 && cnt.structDepth <= 32 && cnt.dictEntryDepth <= 32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cnt depthCounter) EnterArray() *depthCounter {
|
||||||
|
cnt.arrayDepth++
|
||||||
|
return &cnt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cnt depthCounter) EnterStruct() *depthCounter {
|
||||||
|
cnt.structDepth++
|
||||||
|
return &cnt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cnt depthCounter) EnterDictEntry() *depthCounter {
|
||||||
|
cnt.dictEntryDepth++
|
||||||
|
return &cnt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to read a single type from this string. If it was successful, err is nil
|
||||||
|
// and rem is the remaining unparsed part. Otherwise, err is a non-nil
|
||||||
|
// SignatureError and rem is "". depth is the current recursion depth which may
|
||||||
|
// not be greater than 64 and should be given as 0 on the first call.
|
||||||
|
func validSingle(s string, depth *depthCounter) (err error, rem string) { //nolint:staticcheck // Ignore "ST1008: error should be returned as the last argument".
|
||||||
|
if s == "" {
|
||||||
|
return SignatureError{Sig: s, Reason: "empty signature"}, ""
|
||||||
|
}
|
||||||
|
if !depth.Valid() {
|
||||||
|
return SignatureError{Sig: s, Reason: "container nesting too deep"}, ""
|
||||||
|
}
|
||||||
|
switch s[0] {
|
||||||
|
case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 's', 'g', 'o', 'v', 'h':
|
||||||
|
return nil, s[1:]
|
||||||
|
case 'a':
|
||||||
|
if len(s) > 1 && s[1] == '{' {
|
||||||
|
i := findMatching(s[1:], '{', '}')
|
||||||
|
if i == -1 {
|
||||||
|
return SignatureError{Sig: s, Reason: "unmatched '{'"}, ""
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
rem = s[i+1:]
|
||||||
|
s = s[2:i]
|
||||||
|
if len(s) == 0 {
|
||||||
|
return SignatureError{Sig: s, Reason: "empty dict"}, ""
|
||||||
|
}
|
||||||
|
if err, _ = validSingle(s[:1], depth.EnterArray().EnterDictEntry()); err != nil {
|
||||||
|
return err, ""
|
||||||
|
}
|
||||||
|
err, nr := validSingle(s[1:], depth.EnterArray().EnterDictEntry())
|
||||||
|
if err != nil {
|
||||||
|
return err, ""
|
||||||
|
}
|
||||||
|
if nr != "" {
|
||||||
|
return SignatureError{Sig: s, Reason: "too many types in dict"}, ""
|
||||||
|
}
|
||||||
|
return nil, rem
|
||||||
|
}
|
||||||
|
return validSingle(s[1:], depth.EnterArray())
|
||||||
|
case '(':
|
||||||
|
i := findMatching(s, '(', ')')
|
||||||
|
if i == -1 {
|
||||||
|
return SignatureError{Sig: s, Reason: "unmatched ')'"}, ""
|
||||||
|
}
|
||||||
|
rem = s[i+1:]
|
||||||
|
s = s[1:i]
|
||||||
|
for err == nil && s != "" {
|
||||||
|
err, s = validSingle(s, depth.EnterStruct())
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
rem = ""
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return SignatureError{Sig: s, Reason: "invalid type character"}, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func findMatching(s string, left, right rune) int {
|
||||||
|
n := 0
|
||||||
|
for i, v := range s {
|
||||||
|
switch v {
|
||||||
|
case left:
|
||||||
|
n++
|
||||||
|
case right:
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeFor returns the type of the given signature. It ignores any left over
|
||||||
|
// characters and panics if s doesn't start with a valid type signature.
|
||||||
|
func typeFor(s string) (t reflect.Type) {
|
||||||
|
err, _ := validSingle(s, &depthCounter{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, ok := sigToType[s[0]]; ok {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
switch s[0] {
|
||||||
|
case 'a':
|
||||||
|
if s[1] == '{' {
|
||||||
|
i := strings.LastIndex(s, "}")
|
||||||
|
t = reflect.MapOf(sigToType[s[2]], typeFor(s[3:i]))
|
||||||
|
} else {
|
||||||
|
t = reflect.SliceOf(typeFor(s[1:]))
|
||||||
|
}
|
||||||
|
case '(':
|
||||||
|
t = interfacesType
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
6
vendor/github.com/godbus/dbus/v5/transport_darwin.go
generated
vendored
Normal file
6
vendor/github.com/godbus/dbus/v5/transport_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
func (t *unixTransport) SendNullByte() error {
|
||||||
|
_, err := t.Write([]byte{0})
|
||||||
|
return err
|
||||||
|
}
|
||||||
52
vendor/github.com/godbus/dbus/v5/transport_generic.go
generated
vendored
Normal file
52
vendor/github.com/godbus/dbus/v5/transport_generic.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var nativeEndian binary.ByteOrder
|
||||||
|
|
||||||
|
func detectEndianness() binary.ByteOrder {
|
||||||
|
var x uint32 = 0x01020304
|
||||||
|
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
||||||
|
return binary.BigEndian
|
||||||
|
}
|
||||||
|
return binary.LittleEndian
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
nativeEndian = detectEndianness()
|
||||||
|
}
|
||||||
|
|
||||||
|
type genericTransport struct {
|
||||||
|
io.ReadWriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t genericTransport) SendNullByte() error {
|
||||||
|
_, err := t.Write([]byte{0})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t genericTransport) SupportsUnixFDs() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t genericTransport) EnableUnixFDs() {}
|
||||||
|
|
||||||
|
func (t genericTransport) ReadMessage() (*Message, error) {
|
||||||
|
return DecodeMessage(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t genericTransport) SendMessage(msg *Message) error {
|
||||||
|
fds, err := msg.CountFds()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if fds != 0 {
|
||||||
|
return errors.New("dbus: unix fd passing not enabled")
|
||||||
|
}
|
||||||
|
return msg.EncodeTo(t, nativeEndian)
|
||||||
|
}
|
||||||
41
vendor/github.com/godbus/dbus/v5/transport_nonce_tcp.go
generated
vendored
Normal file
41
vendor/github.com/godbus/dbus/v5/transport_nonce_tcp.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
transports["nonce-tcp"] = newNonceTcpTransport
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNonceTcpTransport(keys string) (transport, error) {
|
||||||
|
host := getKey(keys, "host")
|
||||||
|
port := getKey(keys, "port")
|
||||||
|
noncefile := getKey(keys, "noncefile")
|
||||||
|
if host == "" || port == "" || noncefile == "" {
|
||||||
|
return nil, errors.New("dbus: unsupported address (must set host, port and noncefile)")
|
||||||
|
}
|
||||||
|
protocol, err := tcpFamily(keys)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
socket, err := net.Dial(protocol, net.JoinHostPort(host, port))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b, err := os.ReadFile(noncefile)
|
||||||
|
if err != nil {
|
||||||
|
socket.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = socket.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
socket.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewConn(socket)
|
||||||
|
}
|
||||||
41
vendor/github.com/godbus/dbus/v5/transport_tcp.go
generated
vendored
Normal file
41
vendor/github.com/godbus/dbus/v5/transport_tcp.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
transports["tcp"] = newTcpTransport
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcpFamily(keys string) (string, error) {
|
||||||
|
switch getKey(keys, "family") {
|
||||||
|
case "":
|
||||||
|
return "tcp", nil
|
||||||
|
case "ipv4":
|
||||||
|
return "tcp4", nil
|
||||||
|
case "ipv6":
|
||||||
|
return "tcp6", nil
|
||||||
|
default:
|
||||||
|
return "", errors.New("dbus: invalid tcp family (must be ipv4 or ipv6)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTcpTransport(keys string) (transport, error) {
|
||||||
|
host := getKey(keys, "host")
|
||||||
|
port := getKey(keys, "port")
|
||||||
|
if host == "" || port == "" {
|
||||||
|
return nil, errors.New("dbus: unsupported address (must set host and port)")
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol, err := tcpFamily(keys)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
socket, err := net.Dial(protocol, net.JoinHostPort(host, port))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewConn(socket)
|
||||||
|
}
|
||||||
291
vendor/github.com/godbus/dbus/v5/transport_unix.go
generated
vendored
Normal file
291
vendor/github.com/godbus/dbus/v5/transport_unix.go
generated
vendored
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
//go:build !windows && !solaris
|
||||||
|
|
||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// msghead represents the part of the message header
|
||||||
|
// that has a constant size (byte order + 15 bytes).
|
||||||
|
type msghead struct {
|
||||||
|
Type Type
|
||||||
|
Flags Flags
|
||||||
|
Proto byte
|
||||||
|
BodyLen uint32
|
||||||
|
Serial uint32
|
||||||
|
HeaderLen uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type oobReader struct {
|
||||||
|
conn *net.UnixConn
|
||||||
|
oob []byte
|
||||||
|
buf [4096]byte
|
||||||
|
|
||||||
|
// The following fields are used to reduce memory allocs.
|
||||||
|
headers []header
|
||||||
|
csheader []byte
|
||||||
|
b *bytes.Buffer
|
||||||
|
r *bytes.Reader
|
||||||
|
dec *decoder
|
||||||
|
msghead
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *oobReader) Read(b []byte) (n int, err error) {
|
||||||
|
n, oobn, flags, _, err := o.conn.ReadMsgUnix(b, o.buf[:])
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if flags&syscall.MSG_CTRUNC != 0 {
|
||||||
|
return n, errors.New("dbus: control data truncated (too many fds received)")
|
||||||
|
}
|
||||||
|
o.oob = append(o.oob, o.buf[:oobn]...)
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type unixTransport struct {
|
||||||
|
*net.UnixConn
|
||||||
|
rdr *oobReader
|
||||||
|
hasUnixFDs bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUnixTransportFromConn(conn *net.UnixConn) transport {
|
||||||
|
t := new(unixTransport)
|
||||||
|
t.UnixConn = conn
|
||||||
|
t.hasUnixFDs = true
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUnixTransport(keys string) (transport, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
t := new(unixTransport)
|
||||||
|
abstract := getKey(keys, "abstract")
|
||||||
|
path := getKey(keys, "path")
|
||||||
|
switch {
|
||||||
|
case abstract == "" && path == "":
|
||||||
|
return nil, errors.New("dbus: invalid address (neither path nor abstract set)")
|
||||||
|
case abstract != "" && path == "":
|
||||||
|
t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: "@" + abstract, Net: "unix"})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
case abstract == "" && path != "":
|
||||||
|
t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: path, Net: "unix"})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
default:
|
||||||
|
return nil, errors.New("dbus: invalid address (both path and abstract set)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
transports["unix"] = newUnixTransport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *unixTransport) EnableUnixFDs() {
|
||||||
|
t.hasUnixFDs = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *unixTransport) ReadMessage() (*Message, error) {
|
||||||
|
// To be sure that all bytes of out-of-band data are read, we use a special
|
||||||
|
// reader that uses ReadUnix on the underlying connection instead of Read
|
||||||
|
// and gathers the out-of-band data in a buffer.
|
||||||
|
if t.rdr == nil {
|
||||||
|
t.rdr = &oobReader{
|
||||||
|
conn: t.UnixConn,
|
||||||
|
// This buffer is used to decode the part of the header that has a constant size.
|
||||||
|
csheader: make([]byte, 16),
|
||||||
|
b: &bytes.Buffer{},
|
||||||
|
// The reader helps to read from the buffer several times.
|
||||||
|
r: &bytes.Reader{},
|
||||||
|
dec: &decoder{},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.rdr.oob = t.rdr.oob[:0]
|
||||||
|
t.rdr.headers = t.rdr.headers[:0]
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
r = t.rdr.r
|
||||||
|
b = t.rdr.b
|
||||||
|
dec = t.rdr.dec
|
||||||
|
)
|
||||||
|
|
||||||
|
_, err := io.ReadFull(t.rdr, t.rdr.csheader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var order binary.ByteOrder
|
||||||
|
switch t.rdr.csheader[0] {
|
||||||
|
case 'l':
|
||||||
|
order = binary.LittleEndian
|
||||||
|
case 'B':
|
||||||
|
order = binary.BigEndian
|
||||||
|
default:
|
||||||
|
return nil, InvalidMessageError("invalid byte order")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Reset(t.rdr.csheader[1:])
|
||||||
|
if err := binary.Read(r, order, &t.rdr.msghead); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := &Message{
|
||||||
|
Type: t.rdr.Type,
|
||||||
|
Flags: t.rdr.Flags,
|
||||||
|
serial: t.rdr.Serial,
|
||||||
|
}
|
||||||
|
// Length of header fields (without alignment).
|
||||||
|
hlen := t.rdr.HeaderLen
|
||||||
|
if hlen%8 != 0 {
|
||||||
|
hlen += 8 - (hlen % 8)
|
||||||
|
}
|
||||||
|
if hlen+t.rdr.BodyLen+16 > 1<<27 {
|
||||||
|
return nil, InvalidMessageError("message is too long")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode headers and look for unix fds.
|
||||||
|
b.Reset()
|
||||||
|
if _, err = b.Write(t.rdr.csheader[12:]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err = io.CopyN(b, t.rdr, int64(hlen)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dec.Reset(b, order, nil)
|
||||||
|
dec.pos = 12
|
||||||
|
vs, err := dec.Decode(Signature{"a(yv)"})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = Store(vs, &t.rdr.headers); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var unixfds uint32
|
||||||
|
for _, v := range t.rdr.headers {
|
||||||
|
if v.Field == byte(FieldUnixFDs) {
|
||||||
|
unixfds, _ = v.value.(uint32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Headers = make(map[HeaderField]Variant)
|
||||||
|
for _, v := range t.rdr.headers {
|
||||||
|
msg.Headers[HeaderField(v.Field)] = v.Variant
|
||||||
|
}
|
||||||
|
|
||||||
|
dec.align(8)
|
||||||
|
body := make([]byte, t.rdr.BodyLen)
|
||||||
|
if _, err = io.ReadFull(t.rdr, body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.Reset(body)
|
||||||
|
|
||||||
|
if unixfds != 0 {
|
||||||
|
if !t.hasUnixFDs {
|
||||||
|
return nil, errors.New("dbus: got unix fds on unsupported transport")
|
||||||
|
}
|
||||||
|
// read the fds from the OOB data
|
||||||
|
scms, err := syscall.ParseSocketControlMessage(t.rdr.oob)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(scms) != 1 {
|
||||||
|
return nil, errors.New("dbus: received more than one socket control message")
|
||||||
|
}
|
||||||
|
fds, err := syscall.ParseUnixRights(&scms[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dec.Reset(r, order, fds)
|
||||||
|
if err = decodeMessageBody(msg, dec); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// substitute the values in the message body (which are indices for the
|
||||||
|
// array receiver via OOB) with the actual values
|
||||||
|
for i, v := range msg.Body {
|
||||||
|
switch index := v.(type) {
|
||||||
|
case UnixFDIndex:
|
||||||
|
if uint32(index) >= unixfds {
|
||||||
|
return nil, InvalidMessageError("invalid index for unix fd")
|
||||||
|
}
|
||||||
|
msg.Body[i] = UnixFD(fds[index])
|
||||||
|
case []UnixFDIndex:
|
||||||
|
fdArray := make([]UnixFD, len(index))
|
||||||
|
for k, j := range index {
|
||||||
|
if uint32(j) >= unixfds {
|
||||||
|
return nil, InvalidMessageError("invalid index for unix fd")
|
||||||
|
}
|
||||||
|
fdArray[k] = UnixFD(fds[j])
|
||||||
|
}
|
||||||
|
msg.Body[i] = fdArray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dec.Reset(r, order, nil)
|
||||||
|
if err = decodeMessageBody(msg, dec); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeMessageBody(msg *Message, dec *decoder) error {
|
||||||
|
if err := msg.validateHeader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, _ := msg.Headers[FieldSignature].value.(Signature)
|
||||||
|
if sig.str == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
msg.Body, err = dec.Decode(sig)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *unixTransport) SendMessage(msg *Message) error {
|
||||||
|
fdcnt, err := msg.CountFds()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if fdcnt != 0 {
|
||||||
|
if !t.hasUnixFDs {
|
||||||
|
return errors.New("dbus: unix fd passing not enabled")
|
||||||
|
}
|
||||||
|
msg.Headers[FieldUnixFDs] = MakeVariant(uint32(fdcnt))
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
fds, err := msg.EncodeToWithFDs(buf, nativeEndian)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
oob := syscall.UnixRights(fds...)
|
||||||
|
n, oobn, err := t.WriteMsgUnix(buf.Bytes(), oob, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != buf.Len() || oobn != len(oob) {
|
||||||
|
return io.ErrShortWrite
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := msg.EncodeTo(t, nativeEndian); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *unixTransport) SupportsUnixFDs() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
95
vendor/github.com/godbus/dbus/v5/transport_unixcred_dragonfly.go
generated
vendored
Normal file
95
vendor/github.com/godbus/dbus/v5/transport_unixcred_dragonfly.go
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
// The UnixCredentials system call is currently only implemented on Linux
|
||||||
|
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
|
||||||
|
// https://golang.org/s/go1.4-syscall
|
||||||
|
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
|
||||||
|
|
||||||
|
// Local implementation of the UnixCredentials system call for DragonFly BSD
|
||||||
|
|
||||||
|
package dbus
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <sys/ucred.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// http://golang.org/src/pkg/syscall/ztypes_linux_amd64.go
|
||||||
|
// http://golang.org/src/pkg/syscall/ztypes_dragonfly_amd64.go
|
||||||
|
type Ucred struct {
|
||||||
|
Pid int32
|
||||||
|
Uid uint32
|
||||||
|
Gid uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://golang.org/src/pkg/syscall/types_linux.go
|
||||||
|
// http://golang.org/src/pkg/syscall/types_dragonfly.go
|
||||||
|
// https://github.com/DragonFlyBSD/DragonFlyBSD/blob/master/sys/sys/ucred.h
|
||||||
|
const (
|
||||||
|
SizeofUcred = C.sizeof_struct_ucred
|
||||||
|
)
|
||||||
|
|
||||||
|
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
|
||||||
|
func cmsgAlignOf(salen int) int {
|
||||||
|
// From http://golang.org/src/pkg/syscall/sockcmsg_unix.go
|
||||||
|
//salign := sizeofPtr
|
||||||
|
// NOTE: It seems like 64-bit Darwin and DragonFly BSD kernels
|
||||||
|
// still require 32-bit aligned access to network subsystem.
|
||||||
|
//if darwin64Bit || dragonfly64Bit {
|
||||||
|
// salign = 4
|
||||||
|
//}
|
||||||
|
salign := 4
|
||||||
|
return (salen + salign - 1) & ^(salign - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
|
||||||
|
func cmsgData(h *syscall.Cmsghdr) unsafe.Pointer {
|
||||||
|
return unsafe.Pointer(uintptr(unsafe.Pointer(h)) + uintptr(cmsgAlignOf(syscall.SizeofCmsghdr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
|
||||||
|
// UnixCredentials encodes credentials into a socket control message
|
||||||
|
// for sending to another process. This can be used for
|
||||||
|
// authentication.
|
||||||
|
func UnixCredentials(ucred *Ucred) []byte {
|
||||||
|
b := make([]byte, syscall.CmsgSpace(SizeofUcred))
|
||||||
|
h := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
|
||||||
|
h.Level = syscall.SOL_SOCKET
|
||||||
|
h.Type = syscall.SCM_CREDS
|
||||||
|
h.SetLen(syscall.CmsgLen(SizeofUcred))
|
||||||
|
*((*Ucred)(cmsgData(h))) = *ucred
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
|
||||||
|
// ParseUnixCredentials decodes a socket control message that contains
|
||||||
|
// credentials in a Ucred structure. To receive such a message, the
|
||||||
|
// SO_PASSCRED option must be enabled on the socket.
|
||||||
|
func ParseUnixCredentials(m *syscall.SocketControlMessage) (*Ucred, error) {
|
||||||
|
if m.Header.Level != syscall.SOL_SOCKET {
|
||||||
|
return nil, syscall.EINVAL
|
||||||
|
}
|
||||||
|
if m.Header.Type != syscall.SCM_CREDS {
|
||||||
|
return nil, syscall.EINVAL
|
||||||
|
}
|
||||||
|
ucred := *(*Ucred)(unsafe.Pointer(&m.Data[0]))
|
||||||
|
return &ucred, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *unixTransport) SendNullByte() error {
|
||||||
|
ucred := &Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
|
||||||
|
b := UnixCredentials(ucred)
|
||||||
|
_, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if oobn != len(b) {
|
||||||
|
return io.ErrShortWrite
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
94
vendor/github.com/godbus/dbus/v5/transport_unixcred_freebsd.go
generated
vendored
Normal file
94
vendor/github.com/godbus/dbus/v5/transport_unixcred_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
// The UnixCredentials system call is currently only implemented on Linux
|
||||||
|
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
|
||||||
|
// https://golang.org/s/go1.4-syscall
|
||||||
|
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
|
||||||
|
|
||||||
|
// Local implementation of the UnixCredentials system call for FreeBSD
|
||||||
|
|
||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// http://golang.org/src/pkg/syscall/ztypes_linux_amd64.go
|
||||||
|
// https://golang.org/src/syscall/ztypes_freebsd_amd64.go
|
||||||
|
//
|
||||||
|
// Note: FreeBSD actually uses a 'struct cmsgcred' which starts with
|
||||||
|
// these fields and adds a list of the additional groups for the
|
||||||
|
// sender.
|
||||||
|
type Ucred struct {
|
||||||
|
Pid int32
|
||||||
|
Uid uint32
|
||||||
|
Euid uint32
|
||||||
|
Gid uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/freebsd/freebsd/blob/master/sys/sys/socket.h
|
||||||
|
//
|
||||||
|
// The cmsgcred structure contains the above four fields, followed by
|
||||||
|
// a uint16 count of additional groups, uint16 padding to align and a
|
||||||
|
// 16 element array of uint32 for the additional groups. The size is
|
||||||
|
// the same across all supported platforms.
|
||||||
|
const (
|
||||||
|
SizeofCmsgcred = 84 // 4*4 + 2*2 + 16*4
|
||||||
|
)
|
||||||
|
|
||||||
|
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
|
||||||
|
func cmsgAlignOf(salen int) int {
|
||||||
|
salign := unix.SizeofPtr
|
||||||
|
|
||||||
|
return (salen + salign - 1) & ^(salign - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
|
||||||
|
func cmsgData(h *syscall.Cmsghdr) unsafe.Pointer {
|
||||||
|
return unsafe.Pointer(uintptr(unsafe.Pointer(h)) + uintptr(cmsgAlignOf(syscall.SizeofCmsghdr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
|
||||||
|
// UnixCredentials encodes credentials into a socket control message
|
||||||
|
// for sending to another process. This can be used for
|
||||||
|
// authentication.
|
||||||
|
func UnixCredentials(ucred *Ucred) []byte {
|
||||||
|
b := make([]byte, syscall.CmsgSpace(SizeofCmsgcred))
|
||||||
|
h := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
|
||||||
|
h.Level = syscall.SOL_SOCKET
|
||||||
|
h.Type = syscall.SCM_CREDS
|
||||||
|
h.SetLen(syscall.CmsgLen(SizeofCmsgcred))
|
||||||
|
*((*Ucred)(cmsgData(h))) = *ucred
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
|
||||||
|
// ParseUnixCredentials decodes a socket control message that contains
|
||||||
|
// credentials in a Ucred structure. To receive such a message, the
|
||||||
|
// SO_PASSCRED option must be enabled on the socket.
|
||||||
|
func ParseUnixCredentials(m *syscall.SocketControlMessage) (*Ucred, error) {
|
||||||
|
if m.Header.Level != syscall.SOL_SOCKET {
|
||||||
|
return nil, syscall.EINVAL
|
||||||
|
}
|
||||||
|
if m.Header.Type != syscall.SCM_CREDS {
|
||||||
|
return nil, syscall.EINVAL
|
||||||
|
}
|
||||||
|
ucred := *(*Ucred)(unsafe.Pointer(&m.Data[0]))
|
||||||
|
return &ucred, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *unixTransport) SendNullByte() error {
|
||||||
|
ucred := &Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
|
||||||
|
b := UnixCredentials(ucred)
|
||||||
|
_, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if oobn != len(b) {
|
||||||
|
return io.ErrShortWrite
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
25
vendor/github.com/godbus/dbus/v5/transport_unixcred_linux.go
generated
vendored
Normal file
25
vendor/github.com/godbus/dbus/v5/transport_unixcred_linux.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// The UnixCredentials system call is currently only implemented on Linux
|
||||||
|
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
|
||||||
|
// https://golang.org/s/go1.4-syscall
|
||||||
|
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
|
||||||
|
|
||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *unixTransport) SendNullByte() error {
|
||||||
|
ucred := &syscall.Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
|
||||||
|
b := syscall.UnixCredentials(ucred)
|
||||||
|
_, oobn, err := t.WriteMsgUnix([]byte{0}, b, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if oobn != len(b) {
|
||||||
|
return io.ErrShortWrite
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
14
vendor/github.com/godbus/dbus/v5/transport_unixcred_netbsd.go
generated
vendored
Normal file
14
vendor/github.com/godbus/dbus/v5/transport_unixcred_netbsd.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
func (t *unixTransport) SendNullByte() error {
|
||||||
|
n, _, err := t.UnixConn.WriteMsgUnix([]byte{0}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != 1 {
|
||||||
|
return io.ErrShortWrite
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
14
vendor/github.com/godbus/dbus/v5/transport_unixcred_openbsd.go
generated
vendored
Normal file
14
vendor/github.com/godbus/dbus/v5/transport_unixcred_openbsd.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
func (t *unixTransport) SendNullByte() error {
|
||||||
|
n, _, err := t.UnixConn.WriteMsgUnix([]byte{0}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != 1 {
|
||||||
|
return io.ErrShortWrite
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
6
vendor/github.com/godbus/dbus/v5/transport_zos.go
generated
vendored
Normal file
6
vendor/github.com/godbus/dbus/v5/transport_zos.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
func (t *unixTransport) SendNullByte() error {
|
||||||
|
_, err := t.Write([]byte{0})
|
||||||
|
return err
|
||||||
|
}
|
||||||
169
vendor/github.com/godbus/dbus/v5/variant.go
generated
vendored
Normal file
169
vendor/github.com/godbus/dbus/v5/variant.go
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Variant represents the D-Bus variant type.
|
||||||
|
type Variant struct {
|
||||||
|
sig Signature
|
||||||
|
value any
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeVariant converts the given value to a Variant. It panics if v cannot be
|
||||||
|
// represented as a D-Bus type.
|
||||||
|
func MakeVariant(v any) Variant {
|
||||||
|
return MakeVariantWithSignature(v, SignatureOf(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeVariantWithSignature converts the given value to a Variant.
|
||||||
|
func MakeVariantWithSignature(v any, s Signature) Variant {
|
||||||
|
return Variant{s, v}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseVariant parses the given string as a variant as described at
|
||||||
|
// https://developer.gnome.org/glib/stable/gvariant-text.html. If sig is not
|
||||||
|
// empty, it is taken to be the expected signature for the variant.
|
||||||
|
func ParseVariant(s string, sig Signature) (Variant, error) {
|
||||||
|
tokens := varLex(s)
|
||||||
|
p := &varParser{tokens: tokens}
|
||||||
|
n, err := varMakeNode(p)
|
||||||
|
if err != nil {
|
||||||
|
return Variant{}, err
|
||||||
|
}
|
||||||
|
if sig.str == "" {
|
||||||
|
sig, err = varInfer(n)
|
||||||
|
if err != nil {
|
||||||
|
return Variant{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v, err := n.Value(sig)
|
||||||
|
if err != nil {
|
||||||
|
return Variant{}, err
|
||||||
|
}
|
||||||
|
return MakeVariant(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// format returns a formatted version of v and whether this string can be parsed
|
||||||
|
// unambiguously.
|
||||||
|
func (v Variant) format() (string, bool) {
|
||||||
|
switch v.sig.str[0] {
|
||||||
|
case 'b', 'i':
|
||||||
|
return fmt.Sprint(v.value), true
|
||||||
|
case 'n', 'q', 'u', 'x', 't', 'd', 'h':
|
||||||
|
return fmt.Sprint(v.value), false
|
||||||
|
case 's':
|
||||||
|
return strconv.Quote(v.value.(string)), true
|
||||||
|
case 'o':
|
||||||
|
return strconv.Quote(string(v.value.(ObjectPath))), false
|
||||||
|
case 'g':
|
||||||
|
return strconv.Quote(v.value.(Signature).str), false
|
||||||
|
case 'v':
|
||||||
|
s, unamb := v.value.(Variant).format()
|
||||||
|
if !unamb {
|
||||||
|
return "<@" + v.value.(Variant).sig.str + " " + s + ">", true
|
||||||
|
}
|
||||||
|
return "<" + s + ">", true
|
||||||
|
case 'y':
|
||||||
|
return fmt.Sprintf("%#x", v.value.(byte)), false
|
||||||
|
}
|
||||||
|
rv := reflect.ValueOf(v.value)
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
if rv.Len() == 0 {
|
||||||
|
return "[]", false
|
||||||
|
}
|
||||||
|
unamb := true
|
||||||
|
buf := bytes.NewBuffer([]byte("["))
|
||||||
|
for i := 0; i < rv.Len(); i++ {
|
||||||
|
// TODO: slooow
|
||||||
|
s, b := MakeVariant(rv.Index(i).Interface()).format()
|
||||||
|
unamb = unamb && b
|
||||||
|
buf.WriteString(s)
|
||||||
|
if i != rv.Len()-1 {
|
||||||
|
buf.WriteString(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.WriteByte(']')
|
||||||
|
return buf.String(), unamb
|
||||||
|
case reflect.Map:
|
||||||
|
if rv.Len() == 0 {
|
||||||
|
return "{}", false
|
||||||
|
}
|
||||||
|
unamb := true
|
||||||
|
var buf bytes.Buffer
|
||||||
|
kvs := make([]string, rv.Len())
|
||||||
|
for i, k := range rv.MapKeys() {
|
||||||
|
s, b := MakeVariant(k.Interface()).format()
|
||||||
|
unamb = unamb && b
|
||||||
|
buf.Reset()
|
||||||
|
buf.WriteString(s)
|
||||||
|
buf.WriteString(": ")
|
||||||
|
s, b = MakeVariant(rv.MapIndex(k).Interface()).format()
|
||||||
|
unamb = unamb && b
|
||||||
|
buf.WriteString(s)
|
||||||
|
kvs[i] = buf.String()
|
||||||
|
}
|
||||||
|
buf.Reset()
|
||||||
|
buf.WriteByte('{')
|
||||||
|
sort.Strings(kvs)
|
||||||
|
for i, kv := range kvs {
|
||||||
|
if i > 0 {
|
||||||
|
buf.WriteString(", ")
|
||||||
|
}
|
||||||
|
buf.WriteString(kv)
|
||||||
|
}
|
||||||
|
buf.WriteByte('}')
|
||||||
|
return buf.String(), unamb
|
||||||
|
case reflect.Struct:
|
||||||
|
if rv.NumField() == 0 {
|
||||||
|
return "()", false
|
||||||
|
}
|
||||||
|
unamb := true
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteByte('(')
|
||||||
|
for i := 0; i < rv.NumField(); i++ {
|
||||||
|
s, b := MakeVariant(rv.Field(i).Interface()).format()
|
||||||
|
unamb = unamb && b
|
||||||
|
buf.WriteString(s)
|
||||||
|
buf.WriteString(",")
|
||||||
|
if i != rv.NumField()-1 {
|
||||||
|
buf.WriteString(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.WriteByte(')')
|
||||||
|
return buf.String(), unamb
|
||||||
|
|
||||||
|
}
|
||||||
|
return `"INVALID"`, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signature returns the D-Bus signature of the underlying value of v.
|
||||||
|
func (v Variant) Signature() Signature {
|
||||||
|
return v.sig
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of the underlying value of v as
|
||||||
|
// described at https://developer.gnome.org/glib/stable/gvariant-text.html.
|
||||||
|
func (v Variant) String() string {
|
||||||
|
s, unamb := v.format()
|
||||||
|
if !unamb {
|
||||||
|
return "@" + v.sig.str + " " + s
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the underlying value of v.
|
||||||
|
func (v Variant) Value() any {
|
||||||
|
return v.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store converts the variant into a native go type using the same
|
||||||
|
// mechanism as the "Store" function.
|
||||||
|
func (v Variant) Store(value any) error {
|
||||||
|
return storeInterfaces(v.value, value)
|
||||||
|
}
|
||||||
284
vendor/github.com/godbus/dbus/v5/variant_lexer.go
generated
vendored
Normal file
284
vendor/github.com/godbus/dbus/v5/variant_lexer.go
generated
vendored
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Heavily inspired by the lexer from text/template.
|
||||||
|
|
||||||
|
type varToken struct {
|
||||||
|
typ varTokenType
|
||||||
|
val string
|
||||||
|
}
|
||||||
|
|
||||||
|
type varTokenType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
tokEOF varTokenType = iota
|
||||||
|
tokError
|
||||||
|
tokNumber
|
||||||
|
tokString
|
||||||
|
tokBool
|
||||||
|
tokArrayStart
|
||||||
|
tokArrayEnd
|
||||||
|
tokDictStart
|
||||||
|
tokDictEnd
|
||||||
|
tokVariantStart
|
||||||
|
tokVariantEnd
|
||||||
|
tokComma
|
||||||
|
tokColon
|
||||||
|
tokType
|
||||||
|
tokByteString
|
||||||
|
)
|
||||||
|
|
||||||
|
type varLexer struct {
|
||||||
|
input string
|
||||||
|
start int
|
||||||
|
pos int
|
||||||
|
width int
|
||||||
|
tokens []varToken
|
||||||
|
}
|
||||||
|
|
||||||
|
type lexState func(*varLexer) lexState
|
||||||
|
|
||||||
|
func varLex(s string) []varToken {
|
||||||
|
l := &varLexer{input: s}
|
||||||
|
l.run()
|
||||||
|
return l.tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *varLexer) accept(valid string) bool {
|
||||||
|
if strings.ContainsRune(valid, l.next()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *varLexer) backup() {
|
||||||
|
l.pos -= l.width
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *varLexer) emit(t varTokenType) {
|
||||||
|
l.tokens = append(l.tokens, varToken{t, l.input[l.start:l.pos]})
|
||||||
|
l.start = l.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *varLexer) errorf(format string, v ...any) lexState {
|
||||||
|
l.tokens = append(l.tokens, varToken{
|
||||||
|
tokError,
|
||||||
|
fmt.Sprintf(format, v...),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *varLexer) ignore() {
|
||||||
|
l.start = l.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *varLexer) next() rune {
|
||||||
|
var r rune
|
||||||
|
|
||||||
|
if l.pos >= len(l.input) {
|
||||||
|
l.width = 0
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
|
||||||
|
l.pos += l.width
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *varLexer) run() {
|
||||||
|
for state := varLexNormal; state != nil; {
|
||||||
|
state = state(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *varLexer) peek() rune {
|
||||||
|
r := l.next()
|
||||||
|
l.backup()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func varLexNormal(l *varLexer) lexState {
|
||||||
|
for {
|
||||||
|
r := l.next()
|
||||||
|
switch {
|
||||||
|
case r == -1:
|
||||||
|
l.emit(tokEOF)
|
||||||
|
return nil
|
||||||
|
case r == '[':
|
||||||
|
l.emit(tokArrayStart)
|
||||||
|
case r == ']':
|
||||||
|
l.emit(tokArrayEnd)
|
||||||
|
case r == '{':
|
||||||
|
l.emit(tokDictStart)
|
||||||
|
case r == '}':
|
||||||
|
l.emit(tokDictEnd)
|
||||||
|
case r == '<':
|
||||||
|
l.emit(tokVariantStart)
|
||||||
|
case r == '>':
|
||||||
|
l.emit(tokVariantEnd)
|
||||||
|
case r == ':':
|
||||||
|
l.emit(tokColon)
|
||||||
|
case r == ',':
|
||||||
|
l.emit(tokComma)
|
||||||
|
case r == '\'' || r == '"':
|
||||||
|
l.backup()
|
||||||
|
return varLexString
|
||||||
|
case r == '@':
|
||||||
|
l.backup()
|
||||||
|
return varLexType
|
||||||
|
case unicode.IsSpace(r):
|
||||||
|
l.ignore()
|
||||||
|
case unicode.IsNumber(r) || r == '+' || r == '-':
|
||||||
|
l.backup()
|
||||||
|
return varLexNumber
|
||||||
|
case r == 'b':
|
||||||
|
pos := l.start
|
||||||
|
if n := l.peek(); n == '"' || n == '\'' {
|
||||||
|
return varLexByteString
|
||||||
|
}
|
||||||
|
// not a byte string; try to parse it as a type or bool below
|
||||||
|
l.pos = pos + 1
|
||||||
|
l.width = 1
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
// either a bool or a type. Try bools first.
|
||||||
|
l.backup()
|
||||||
|
if l.pos+4 <= len(l.input) {
|
||||||
|
if l.input[l.pos:l.pos+4] == "true" {
|
||||||
|
l.pos += 4
|
||||||
|
l.emit(tokBool)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.pos+5 <= len(l.input) {
|
||||||
|
if l.input[l.pos:l.pos+5] == "false" {
|
||||||
|
l.pos += 5
|
||||||
|
l.emit(tokBool)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// must be a type.
|
||||||
|
return varLexType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var varTypeMap = map[string]string{
|
||||||
|
"boolean": "b",
|
||||||
|
"byte": "y",
|
||||||
|
"int16": "n",
|
||||||
|
"uint16": "q",
|
||||||
|
"int32": "i",
|
||||||
|
"uint32": "u",
|
||||||
|
"int64": "x",
|
||||||
|
"uint64": "t",
|
||||||
|
"double": "f",
|
||||||
|
"string": "s",
|
||||||
|
"objectpath": "o",
|
||||||
|
"signature": "g",
|
||||||
|
}
|
||||||
|
|
||||||
|
func varLexByteString(l *varLexer) lexState {
|
||||||
|
q := l.next()
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
switch l.next() {
|
||||||
|
case '\\':
|
||||||
|
if r := l.next(); r != -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case -1:
|
||||||
|
return l.errorf("unterminated bytestring")
|
||||||
|
case q:
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.emit(tokByteString)
|
||||||
|
return varLexNormal
|
||||||
|
}
|
||||||
|
|
||||||
|
func varLexNumber(l *varLexer) lexState {
|
||||||
|
l.accept("+-")
|
||||||
|
digits := "0123456789"
|
||||||
|
if l.accept("0") {
|
||||||
|
if l.accept("x") {
|
||||||
|
digits = "0123456789abcdefABCDEF"
|
||||||
|
} else {
|
||||||
|
digits = "01234567"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for strings.ContainsRune(digits, l.next()) {
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
if l.accept(".") {
|
||||||
|
for strings.ContainsRune(digits, l.next()) {
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
}
|
||||||
|
if l.accept("eE") {
|
||||||
|
l.accept("+-")
|
||||||
|
for strings.ContainsRune("0123456789", l.next()) {
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
}
|
||||||
|
if r := l.peek(); unicode.IsLetter(r) {
|
||||||
|
l.next()
|
||||||
|
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||||
|
}
|
||||||
|
l.emit(tokNumber)
|
||||||
|
return varLexNormal
|
||||||
|
}
|
||||||
|
|
||||||
|
func varLexString(l *varLexer) lexState {
|
||||||
|
q := l.next()
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
switch l.next() {
|
||||||
|
case '\\':
|
||||||
|
if r := l.next(); r != -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case -1:
|
||||||
|
return l.errorf("unterminated string")
|
||||||
|
case q:
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.emit(tokString)
|
||||||
|
return varLexNormal
|
||||||
|
}
|
||||||
|
|
||||||
|
func varLexType(l *varLexer) lexState {
|
||||||
|
at := l.accept("@")
|
||||||
|
for {
|
||||||
|
r := l.next()
|
||||||
|
if r == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if unicode.IsSpace(r) {
|
||||||
|
l.backup()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if at {
|
||||||
|
if _, err := ParseSignature(l.input[l.start+1 : l.pos]); err != nil {
|
||||||
|
return l.errorf("%s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, ok := varTypeMap[l.input[l.start:l.pos]]; ok {
|
||||||
|
l.emit(tokType)
|
||||||
|
return varLexNormal
|
||||||
|
}
|
||||||
|
return l.errorf("unrecognized type %q", l.input[l.start:l.pos])
|
||||||
|
}
|
||||||
|
l.emit(tokType)
|
||||||
|
return varLexNormal
|
||||||
|
}
|
||||||
815
vendor/github.com/godbus/dbus/v5/variant_parser.go
generated
vendored
Normal file
815
vendor/github.com/godbus/dbus/v5/variant_parser.go
generated
vendored
Normal file
@@ -0,0 +1,815 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type varParser struct {
|
||||||
|
tokens []varToken
|
||||||
|
i int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *varParser) backup() {
|
||||||
|
p.i--
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *varParser) next() varToken {
|
||||||
|
if p.i < len(p.tokens) {
|
||||||
|
t := p.tokens[p.i]
|
||||||
|
p.i++
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
return varToken{typ: tokEOF}
|
||||||
|
}
|
||||||
|
|
||||||
|
type varNode interface {
|
||||||
|
Infer() (Signature, error)
|
||||||
|
String() string
|
||||||
|
Sigs() sigSet
|
||||||
|
Value(Signature) (any, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func varMakeNode(p *varParser) (varNode, error) {
|
||||||
|
var sig Signature
|
||||||
|
|
||||||
|
for {
|
||||||
|
t := p.next()
|
||||||
|
switch t.typ {
|
||||||
|
case tokEOF:
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
case tokError:
|
||||||
|
return nil, errors.New(t.val)
|
||||||
|
case tokNumber:
|
||||||
|
return varMakeNumNode(t, sig)
|
||||||
|
case tokString:
|
||||||
|
return varMakeStringNode(t, sig)
|
||||||
|
case tokBool:
|
||||||
|
if sig.str != "" && sig.str != "b" {
|
||||||
|
return nil, varTypeError{t.val, sig}
|
||||||
|
}
|
||||||
|
b, err := strconv.ParseBool(t.val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return boolNode(b), nil
|
||||||
|
case tokArrayStart:
|
||||||
|
return varMakeArrayNode(p, sig)
|
||||||
|
case tokVariantStart:
|
||||||
|
return varMakeVariantNode(p, sig)
|
||||||
|
case tokDictStart:
|
||||||
|
return varMakeDictNode(p, sig)
|
||||||
|
case tokType:
|
||||||
|
if sig.str != "" {
|
||||||
|
return nil, errors.New("unexpected type annotation")
|
||||||
|
}
|
||||||
|
if t.val[0] == '@' {
|
||||||
|
sig.str = t.val[1:]
|
||||||
|
} else {
|
||||||
|
sig.str = varTypeMap[t.val]
|
||||||
|
}
|
||||||
|
case tokByteString:
|
||||||
|
if sig.str != "" && sig.str != "ay" {
|
||||||
|
return nil, varTypeError{t.val, sig}
|
||||||
|
}
|
||||||
|
b, err := varParseByteString(t.val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return byteStringNode(b), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected %q", t.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type varTypeError struct {
|
||||||
|
val string
|
||||||
|
sig Signature
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e varTypeError) Error() string {
|
||||||
|
return fmt.Sprintf("dbus: can't parse %q as type %q", e.val, e.sig.str)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sigSet map[Signature]bool
|
||||||
|
|
||||||
|
func (s sigSet) Empty() bool {
|
||||||
|
return len(s) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sigSet) Intersect(s2 sigSet) sigSet {
|
||||||
|
r := make(sigSet)
|
||||||
|
for k := range s {
|
||||||
|
if s2[k] {
|
||||||
|
r[k] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sigSet) Single() (Signature, bool) {
|
||||||
|
if len(s) == 1 {
|
||||||
|
for k := range s {
|
||||||
|
return k, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Signature{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sigSet) ToArray() sigSet {
|
||||||
|
r := make(sigSet, len(s))
|
||||||
|
for k := range s {
|
||||||
|
r[Signature{"a" + k.str}] = true
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
type numNode struct {
|
||||||
|
sig Signature
|
||||||
|
str string
|
||||||
|
val any
|
||||||
|
}
|
||||||
|
|
||||||
|
var numSigSet = sigSet{
|
||||||
|
Signature{"y"}: true,
|
||||||
|
Signature{"n"}: true,
|
||||||
|
Signature{"q"}: true,
|
||||||
|
Signature{"i"}: true,
|
||||||
|
Signature{"u"}: true,
|
||||||
|
Signature{"x"}: true,
|
||||||
|
Signature{"t"}: true,
|
||||||
|
Signature{"d"}: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n numNode) Infer() (Signature, error) {
|
||||||
|
if strings.ContainsAny(n.str, ".e") {
|
||||||
|
return Signature{"d"}, nil
|
||||||
|
}
|
||||||
|
return Signature{"i"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n numNode) String() string {
|
||||||
|
return n.str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n numNode) Sigs() sigSet {
|
||||||
|
if n.sig.str != "" {
|
||||||
|
return sigSet{n.sig: true}
|
||||||
|
}
|
||||||
|
if strings.ContainsAny(n.str, ".e") {
|
||||||
|
return sigSet{Signature{"d"}: true}
|
||||||
|
}
|
||||||
|
return numSigSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n numNode) Value(sig Signature) (any, error) {
|
||||||
|
if n.sig.str != "" && n.sig != sig {
|
||||||
|
return nil, varTypeError{n.str, sig}
|
||||||
|
}
|
||||||
|
if n.val != nil {
|
||||||
|
return n.val, nil
|
||||||
|
}
|
||||||
|
return varNumAs(n.str, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func varMakeNumNode(tok varToken, sig Signature) (varNode, error) {
|
||||||
|
if sig.str == "" {
|
||||||
|
return numNode{str: tok.val}, nil
|
||||||
|
}
|
||||||
|
num, err := varNumAs(tok.val, sig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return numNode{sig: sig, val: num}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func varNumAs(s string, sig Signature) (any, error) {
|
||||||
|
isUnsigned := false
|
||||||
|
size := 32
|
||||||
|
switch sig.str {
|
||||||
|
case "n":
|
||||||
|
size = 16
|
||||||
|
case "i":
|
||||||
|
case "x":
|
||||||
|
size = 64
|
||||||
|
case "y":
|
||||||
|
size = 8
|
||||||
|
isUnsigned = true
|
||||||
|
case "q":
|
||||||
|
size = 16
|
||||||
|
isUnsigned = true
|
||||||
|
case "u":
|
||||||
|
isUnsigned = true
|
||||||
|
case "t":
|
||||||
|
size = 64
|
||||||
|
isUnsigned = true
|
||||||
|
case "d":
|
||||||
|
d, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
default:
|
||||||
|
return nil, varTypeError{s, sig}
|
||||||
|
}
|
||||||
|
base := 10
|
||||||
|
if after, ok := strings.CutPrefix(s, "0x"); ok {
|
||||||
|
base = 16
|
||||||
|
s = after
|
||||||
|
}
|
||||||
|
if after, ok := strings.CutPrefix(s, "0"); ok && len(s) != 1 {
|
||||||
|
base = 8
|
||||||
|
s = after
|
||||||
|
}
|
||||||
|
if isUnsigned {
|
||||||
|
i, err := strconv.ParseUint(s, base, size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var v any = i
|
||||||
|
switch sig.str {
|
||||||
|
case "y":
|
||||||
|
v = byte(i)
|
||||||
|
case "q":
|
||||||
|
v = uint16(i)
|
||||||
|
case "u":
|
||||||
|
v = uint32(i)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
i, err := strconv.ParseInt(s, base, size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var v any = i
|
||||||
|
switch sig.str {
|
||||||
|
case "n":
|
||||||
|
v = int16(i)
|
||||||
|
case "i":
|
||||||
|
v = int32(i)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringNode struct {
|
||||||
|
sig Signature
|
||||||
|
str string // parsed
|
||||||
|
val any // has correct type
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringSigSet = sigSet{
|
||||||
|
Signature{"s"}: true,
|
||||||
|
Signature{"g"}: true,
|
||||||
|
Signature{"o"}: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n stringNode) Infer() (Signature, error) {
|
||||||
|
return Signature{"s"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n stringNode) String() string {
|
||||||
|
return n.str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n stringNode) Sigs() sigSet {
|
||||||
|
if n.sig.str != "" {
|
||||||
|
return sigSet{n.sig: true}
|
||||||
|
}
|
||||||
|
return stringSigSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n stringNode) Value(sig Signature) (any, error) {
|
||||||
|
if n.sig.str != "" && n.sig != sig {
|
||||||
|
return nil, varTypeError{n.str, sig}
|
||||||
|
}
|
||||||
|
if n.val != nil {
|
||||||
|
return n.val, nil
|
||||||
|
}
|
||||||
|
switch sig.str {
|
||||||
|
case "g":
|
||||||
|
return Signature{n.str}, nil
|
||||||
|
case "o":
|
||||||
|
return ObjectPath(n.str), nil
|
||||||
|
case "s":
|
||||||
|
return n.str, nil
|
||||||
|
default:
|
||||||
|
return nil, varTypeError{n.str, sig}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func varMakeStringNode(tok varToken, sig Signature) (varNode, error) {
|
||||||
|
if sig.str != "" && sig.str != "s" && sig.str != "g" && sig.str != "o" {
|
||||||
|
return nil, fmt.Errorf("invalid type %q for string", sig.str)
|
||||||
|
}
|
||||||
|
s, err := varParseString(tok.val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
n := stringNode{str: s}
|
||||||
|
if sig.str == "" {
|
||||||
|
return stringNode{str: s}, nil
|
||||||
|
}
|
||||||
|
n.sig = sig
|
||||||
|
switch sig.str {
|
||||||
|
case "o":
|
||||||
|
n.val = ObjectPath(s)
|
||||||
|
case "g":
|
||||||
|
n.val = Signature{s}
|
||||||
|
case "s":
|
||||||
|
n.val = s
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func varParseString(s string) (string, error) {
|
||||||
|
// quotes are guaranteed to be there
|
||||||
|
s = s[1 : len(s)-1]
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
for len(s) != 0 {
|
||||||
|
r, size := utf8.DecodeRuneInString(s)
|
||||||
|
if r == utf8.RuneError && size == 1 {
|
||||||
|
return "", errors.New("invalid UTF-8")
|
||||||
|
}
|
||||||
|
s = s[size:]
|
||||||
|
if r != '\\' {
|
||||||
|
buf.WriteRune(r)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r, size = utf8.DecodeRuneInString(s)
|
||||||
|
if r == utf8.RuneError && size == 1 {
|
||||||
|
return "", errors.New("invalid UTF-8")
|
||||||
|
}
|
||||||
|
s = s[size:]
|
||||||
|
switch r {
|
||||||
|
case 'a':
|
||||||
|
buf.WriteRune(0x7)
|
||||||
|
case 'b':
|
||||||
|
buf.WriteRune(0x8)
|
||||||
|
case 'f':
|
||||||
|
buf.WriteRune(0xc)
|
||||||
|
case 'n':
|
||||||
|
buf.WriteRune('\n')
|
||||||
|
case 'r':
|
||||||
|
buf.WriteRune('\r')
|
||||||
|
case 't':
|
||||||
|
buf.WriteRune('\t')
|
||||||
|
case '\n':
|
||||||
|
case 'u':
|
||||||
|
if len(s) < 4 {
|
||||||
|
return "", errors.New("short unicode escape")
|
||||||
|
}
|
||||||
|
r, err := strconv.ParseUint(s[:4], 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
buf.WriteRune(rune(r))
|
||||||
|
s = s[4:]
|
||||||
|
case 'U':
|
||||||
|
if len(s) < 8 {
|
||||||
|
return "", errors.New("short unicode escape")
|
||||||
|
}
|
||||||
|
r, err := strconv.ParseUint(s[:8], 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
buf.WriteRune(rune(r))
|
||||||
|
s = s[8:]
|
||||||
|
default:
|
||||||
|
buf.WriteRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var boolSigSet = sigSet{Signature{"b"}: true}
|
||||||
|
|
||||||
|
type boolNode bool
|
||||||
|
|
||||||
|
func (boolNode) Infer() (Signature, error) {
|
||||||
|
return Signature{"b"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b boolNode) String() string {
|
||||||
|
if b {
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
return "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (boolNode) Sigs() sigSet {
|
||||||
|
return boolSigSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b boolNode) Value(sig Signature) (any, error) {
|
||||||
|
if sig.str != "b" {
|
||||||
|
return nil, varTypeError{b.String(), sig}
|
||||||
|
}
|
||||||
|
return bool(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type arrayNode struct {
|
||||||
|
set sigSet
|
||||||
|
children []varNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n arrayNode) Infer() (Signature, error) {
|
||||||
|
for _, v := range n.children {
|
||||||
|
csig, err := varInfer(v)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return Signature{"a" + csig.str}, nil
|
||||||
|
}
|
||||||
|
return Signature{}, fmt.Errorf("can't infer type for %q", n.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n arrayNode) String() string {
|
||||||
|
s := "["
|
||||||
|
for i, v := range n.children {
|
||||||
|
s += v.String()
|
||||||
|
if i != len(n.children)-1 {
|
||||||
|
s += ", "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n arrayNode) Sigs() sigSet {
|
||||||
|
return n.set
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n arrayNode) Value(sig Signature) (any, error) {
|
||||||
|
if n.set.Empty() {
|
||||||
|
// no type information whatsoever, so this must be an empty slice
|
||||||
|
return reflect.MakeSlice(typeFor(sig.str), 0, 0).Interface(), nil
|
||||||
|
}
|
||||||
|
if !n.set[sig] {
|
||||||
|
return nil, varTypeError{n.String(), sig}
|
||||||
|
}
|
||||||
|
s := reflect.MakeSlice(typeFor(sig.str), len(n.children), len(n.children))
|
||||||
|
for i, v := range n.children {
|
||||||
|
rv, err := v.Value(Signature{sig.str[1:]})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.Index(i).Set(reflect.ValueOf(rv))
|
||||||
|
}
|
||||||
|
return s.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func varMakeArrayNode(p *varParser, sig Signature) (varNode, error) {
|
||||||
|
var n arrayNode
|
||||||
|
if sig.str != "" {
|
||||||
|
n.set = sigSet{sig: true}
|
||||||
|
}
|
||||||
|
if t := p.next(); t.typ == tokArrayEnd {
|
||||||
|
return n, nil
|
||||||
|
} else {
|
||||||
|
p.backup()
|
||||||
|
}
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
t := p.next()
|
||||||
|
switch t.typ {
|
||||||
|
case tokEOF:
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
case tokError:
|
||||||
|
return nil, errors.New(t.val)
|
||||||
|
}
|
||||||
|
p.backup()
|
||||||
|
cn, err := varMakeNode(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cset := cn.Sigs(); !cset.Empty() {
|
||||||
|
if n.set.Empty() {
|
||||||
|
n.set = cset.ToArray()
|
||||||
|
} else {
|
||||||
|
nset := cset.ToArray().Intersect(n.set)
|
||||||
|
if nset.Empty() {
|
||||||
|
return nil, fmt.Errorf("can't parse %q with given type information", cn.String())
|
||||||
|
}
|
||||||
|
n.set = nset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.children = append(n.children, cn)
|
||||||
|
switch t := p.next(); t.typ {
|
||||||
|
case tokEOF:
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
case tokError:
|
||||||
|
return nil, errors.New(t.val)
|
||||||
|
case tokArrayEnd:
|
||||||
|
break Loop
|
||||||
|
case tokComma:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected %q", t.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type variantNode struct {
|
||||||
|
n varNode
|
||||||
|
}
|
||||||
|
|
||||||
|
var variantSet = sigSet{
|
||||||
|
Signature{"v"}: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (variantNode) Infer() (Signature, error) {
|
||||||
|
return Signature{"v"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n variantNode) String() string {
|
||||||
|
return "<" + n.n.String() + ">"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (variantNode) Sigs() sigSet {
|
||||||
|
return variantSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n variantNode) Value(sig Signature) (any, error) {
|
||||||
|
if sig.str != "v" {
|
||||||
|
return nil, varTypeError{n.String(), sig}
|
||||||
|
}
|
||||||
|
sig, err := varInfer(n.n)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
v, err := n.n.Value(sig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return MakeVariant(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func varMakeVariantNode(p *varParser, sig Signature) (varNode, error) {
|
||||||
|
n, err := varMakeNode(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if t := p.next(); t.typ != tokVariantEnd {
|
||||||
|
return nil, fmt.Errorf("unexpected %q", t.val)
|
||||||
|
}
|
||||||
|
vn := variantNode{n}
|
||||||
|
if sig.str != "" && sig.str != "v" {
|
||||||
|
return nil, varTypeError{vn.String(), sig}
|
||||||
|
}
|
||||||
|
return variantNode{n}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dictEntry struct {
|
||||||
|
key, val varNode
|
||||||
|
}
|
||||||
|
|
||||||
|
type dictNode struct {
|
||||||
|
kset, vset sigSet
|
||||||
|
children []dictEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n dictNode) Infer() (Signature, error) {
|
||||||
|
for _, v := range n.children {
|
||||||
|
ksig, err := varInfer(v.key)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
vsig, err := varInfer(v.val)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return Signature{"a{" + ksig.str + vsig.str + "}"}, nil
|
||||||
|
}
|
||||||
|
return Signature{}, fmt.Errorf("can't infer type for %q", n.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n dictNode) String() string {
|
||||||
|
s := "{"
|
||||||
|
for i, v := range n.children {
|
||||||
|
s += v.key.String() + ": " + v.val.String()
|
||||||
|
if i != len(n.children)-1 {
|
||||||
|
s += ", "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s + "}"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n dictNode) Sigs() sigSet {
|
||||||
|
r := sigSet{}
|
||||||
|
for k := range n.kset {
|
||||||
|
for v := range n.vset {
|
||||||
|
sig := "a{" + k.str + v.str + "}"
|
||||||
|
r[Signature{sig}] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n dictNode) Value(sig Signature) (any, error) {
|
||||||
|
set := n.Sigs()
|
||||||
|
if set.Empty() {
|
||||||
|
// no type information -> empty dict
|
||||||
|
return reflect.MakeMap(typeFor(sig.str)).Interface(), nil
|
||||||
|
}
|
||||||
|
if !set[sig] {
|
||||||
|
return nil, varTypeError{n.String(), sig}
|
||||||
|
}
|
||||||
|
m := reflect.MakeMap(typeFor(sig.str))
|
||||||
|
ksig := Signature{sig.str[2:3]}
|
||||||
|
vsig := Signature{sig.str[3 : len(sig.str)-1]}
|
||||||
|
for _, v := range n.children {
|
||||||
|
kv, err := v.key.Value(ksig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
vv, err := v.val.Value(vsig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv))
|
||||||
|
}
|
||||||
|
return m.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func varMakeDictNode(p *varParser, sig Signature) (varNode, error) {
|
||||||
|
var n dictNode
|
||||||
|
|
||||||
|
if sig.str != "" {
|
||||||
|
if len(sig.str) < 5 {
|
||||||
|
return nil, fmt.Errorf("invalid signature %q for dict type", sig)
|
||||||
|
}
|
||||||
|
ksig := Signature{string(sig.str[2])}
|
||||||
|
vsig := Signature{sig.str[3 : len(sig.str)-1]}
|
||||||
|
n.kset = sigSet{ksig: true}
|
||||||
|
n.vset = sigSet{vsig: true}
|
||||||
|
}
|
||||||
|
if t := p.next(); t.typ == tokDictEnd {
|
||||||
|
return n, nil
|
||||||
|
} else {
|
||||||
|
p.backup()
|
||||||
|
}
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
t := p.next()
|
||||||
|
switch t.typ {
|
||||||
|
case tokEOF:
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
case tokError:
|
||||||
|
return nil, errors.New(t.val)
|
||||||
|
}
|
||||||
|
p.backup()
|
||||||
|
kn, err := varMakeNode(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if kset := kn.Sigs(); !kset.Empty() {
|
||||||
|
if n.kset.Empty() {
|
||||||
|
n.kset = kset
|
||||||
|
} else {
|
||||||
|
n.kset = kset.Intersect(n.kset)
|
||||||
|
if n.kset.Empty() {
|
||||||
|
return nil, fmt.Errorf("can't parse %q with given type information", kn.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t = p.next()
|
||||||
|
switch t.typ {
|
||||||
|
case tokEOF:
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
case tokError:
|
||||||
|
return nil, errors.New(t.val)
|
||||||
|
case tokColon:
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected %q", t.val)
|
||||||
|
}
|
||||||
|
t = p.next()
|
||||||
|
switch t.typ {
|
||||||
|
case tokEOF:
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
case tokError:
|
||||||
|
return nil, errors.New(t.val)
|
||||||
|
}
|
||||||
|
p.backup()
|
||||||
|
vn, err := varMakeNode(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if vset := vn.Sigs(); !vset.Empty() {
|
||||||
|
if n.vset.Empty() {
|
||||||
|
n.vset = vset
|
||||||
|
} else {
|
||||||
|
n.vset = n.vset.Intersect(vset)
|
||||||
|
if n.vset.Empty() {
|
||||||
|
return nil, fmt.Errorf("can't parse %q with given type information", vn.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.children = append(n.children, dictEntry{kn, vn})
|
||||||
|
t = p.next()
|
||||||
|
switch t.typ {
|
||||||
|
case tokEOF:
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
case tokError:
|
||||||
|
return nil, errors.New(t.val)
|
||||||
|
case tokDictEnd:
|
||||||
|
break Loop
|
||||||
|
case tokComma:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected %q", t.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type byteStringNode []byte
|
||||||
|
|
||||||
|
var byteStringSet = sigSet{
|
||||||
|
Signature{"ay"}: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (byteStringNode) Infer() (Signature, error) {
|
||||||
|
return Signature{"ay"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b byteStringNode) String() string {
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b byteStringNode) Sigs() sigSet {
|
||||||
|
return byteStringSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b byteStringNode) Value(sig Signature) (any, error) {
|
||||||
|
if sig.str != "ay" {
|
||||||
|
return nil, varTypeError{b.String(), sig}
|
||||||
|
}
|
||||||
|
return []byte(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func varParseByteString(s string) ([]byte, error) {
|
||||||
|
// quotes and b at start are guaranteed to be there
|
||||||
|
b := make([]byte, 0, 1)
|
||||||
|
s = s[2 : len(s)-1]
|
||||||
|
for len(s) != 0 {
|
||||||
|
c := s[0]
|
||||||
|
s = s[1:]
|
||||||
|
if c != '\\' {
|
||||||
|
b = append(b, c)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c = s[0]
|
||||||
|
s = s[1:]
|
||||||
|
switch c {
|
||||||
|
case 'a':
|
||||||
|
b = append(b, 0x7)
|
||||||
|
case 'b':
|
||||||
|
b = append(b, 0x8)
|
||||||
|
case 'f':
|
||||||
|
b = append(b, 0xc)
|
||||||
|
case 'n':
|
||||||
|
b = append(b, '\n')
|
||||||
|
case 'r':
|
||||||
|
b = append(b, '\r')
|
||||||
|
case 't':
|
||||||
|
b = append(b, '\t')
|
||||||
|
case 'x':
|
||||||
|
if len(s) < 2 {
|
||||||
|
return nil, errors.New("short escape")
|
||||||
|
}
|
||||||
|
n, err := strconv.ParseUint(s[:2], 16, 8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b = append(b, byte(n))
|
||||||
|
s = s[2:]
|
||||||
|
case '0':
|
||||||
|
if len(s) < 3 {
|
||||||
|
return nil, errors.New("short escape")
|
||||||
|
}
|
||||||
|
n, err := strconv.ParseUint(s[:3], 8, 8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b = append(b, byte(n))
|
||||||
|
s = s[3:]
|
||||||
|
default:
|
||||||
|
b = append(b, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(b, 0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func varInfer(n varNode) (Signature, error) {
|
||||||
|
if sig, ok := n.Sigs().Single(); ok {
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
return n.Infer()
|
||||||
|
}
|
||||||
201
vendor/github.com/inconshreveable/mousetrap/LICENSE
generated
vendored
Normal file
201
vendor/github.com/inconshreveable/mousetrap/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2022 Alan Shreve (@inconshreveable)
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
23
vendor/github.com/inconshreveable/mousetrap/README.md
generated
vendored
Normal file
23
vendor/github.com/inconshreveable/mousetrap/README.md
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# mousetrap
|
||||||
|
|
||||||
|
mousetrap is a tiny library that answers a single question.
|
||||||
|
|
||||||
|
On a Windows machine, was the process invoked by someone double clicking on
|
||||||
|
the executable file while browsing in explorer?
|
||||||
|
|
||||||
|
### Motivation
|
||||||
|
|
||||||
|
Windows developers unfamiliar with command line tools will often "double-click"
|
||||||
|
the executable for a tool. Because most CLI tools print the help and then exit
|
||||||
|
when invoked without arguments, this is often very frustrating for those users.
|
||||||
|
|
||||||
|
mousetrap provides a way to detect these invocations so that you can provide
|
||||||
|
more helpful behavior and instructions on how to run the CLI tool. To see what
|
||||||
|
this looks like, both from an organizational and a technical perspective, see
|
||||||
|
https://inconshreveable.com/09-09-2014/sweat-the-small-stuff/
|
||||||
|
|
||||||
|
### The interface
|
||||||
|
|
||||||
|
The library exposes a single interface:
|
||||||
|
|
||||||
|
func StartedByExplorer() (bool)
|
||||||
16
vendor/github.com/inconshreveable/mousetrap/trap_others.go
generated
vendored
Normal file
16
vendor/github.com/inconshreveable/mousetrap/trap_others.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package mousetrap
|
||||||
|
|
||||||
|
// StartedByExplorer returns true if the program was invoked by the user
|
||||||
|
// double-clicking on the executable from explorer.exe
|
||||||
|
//
|
||||||
|
// It is conservative and returns false if any of the internal calls fail.
|
||||||
|
// It does not guarantee that the program was run from a terminal. It only can tell you
|
||||||
|
// whether it was launched from explorer.exe
|
||||||
|
//
|
||||||
|
// On non-Windows platforms, it always returns false.
|
||||||
|
func StartedByExplorer() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
42
vendor/github.com/inconshreveable/mousetrap/trap_windows.go
generated
vendored
Normal file
42
vendor/github.com/inconshreveable/mousetrap/trap_windows.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package mousetrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getProcessEntry(pid int) (*syscall.ProcessEntry32, error) {
|
||||||
|
snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer syscall.CloseHandle(snapshot)
|
||||||
|
var procEntry syscall.ProcessEntry32
|
||||||
|
procEntry.Size = uint32(unsafe.Sizeof(procEntry))
|
||||||
|
if err = syscall.Process32First(snapshot, &procEntry); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if procEntry.ProcessID == uint32(pid) {
|
||||||
|
return &procEntry, nil
|
||||||
|
}
|
||||||
|
err = syscall.Process32Next(snapshot, &procEntry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartedByExplorer returns true if the program was invoked by the user double-clicking
|
||||||
|
// on the executable from explorer.exe
|
||||||
|
//
|
||||||
|
// It is conservative and returns false if any of the internal calls fail.
|
||||||
|
// It does not guarantee that the program was run from a terminal. It only can tell you
|
||||||
|
// whether it was launched from explorer.exe
|
||||||
|
func StartedByExplorer() bool {
|
||||||
|
pe, err := getProcessEntry(syscall.Getppid())
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return "explorer.exe" == syscall.UTF16ToString(pe.ExeFile[:])
|
||||||
|
}
|
||||||
39
vendor/github.com/spf13/cobra/.gitignore
generated
vendored
Normal file
39
vendor/github.com/spf13/cobra/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
# Vim files https://github.com/github/gitignore/blob/master/Global/Vim.gitignore
|
||||||
|
# swap
|
||||||
|
[._]*.s[a-w][a-z]
|
||||||
|
[._]s[a-w][a-z]
|
||||||
|
# session
|
||||||
|
Session.vim
|
||||||
|
# temporary
|
||||||
|
.netrwhist
|
||||||
|
*~
|
||||||
|
# auto-generated tag files
|
||||||
|
tags
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
cobra.test
|
||||||
|
bin
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
66
vendor/github.com/spf13/cobra/.golangci.yml
generated
vendored
Normal file
66
vendor/github.com/spf13/cobra/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Copyright 2013-2023 The Cobra Authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
version: "2"
|
||||||
|
|
||||||
|
run:
|
||||||
|
timeout: 5m
|
||||||
|
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
|
||||||
|
linters:
|
||||||
|
default: none
|
||||||
|
enable:
|
||||||
|
#- bodyclose
|
||||||
|
#- depguard
|
||||||
|
#- dogsled
|
||||||
|
#- dupl
|
||||||
|
- errcheck
|
||||||
|
#- exhaustive
|
||||||
|
#- funlen
|
||||||
|
#- gochecknoinits
|
||||||
|
- goconst
|
||||||
|
- gocritic
|
||||||
|
#- gocyclo
|
||||||
|
#- goprintffuncname
|
||||||
|
- gosec
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
#- lll
|
||||||
|
- misspell
|
||||||
|
#- mnd
|
||||||
|
#- nakedret
|
||||||
|
#- noctx
|
||||||
|
- nolintlint
|
||||||
|
#- rowserrcheck
|
||||||
|
- staticcheck
|
||||||
|
- unconvert
|
||||||
|
#- unparam
|
||||||
|
- unused
|
||||||
|
#- whitespace
|
||||||
|
exclusions:
|
||||||
|
presets:
|
||||||
|
- common-false-positives
|
||||||
|
- legacy
|
||||||
|
- std-error-handling
|
||||||
|
settings:
|
||||||
|
govet:
|
||||||
|
# Disable buildtag check to allow dual build tag syntax (both //go:build and // +build).
|
||||||
|
# This is necessary for Go 1.15 compatibility since //go:build was introduced in Go 1.17.
|
||||||
|
# This can be removed once Cobra requires Go 1.17 or higher.
|
||||||
|
disable:
|
||||||
|
- buildtag
|
||||||
3
vendor/github.com/spf13/cobra/.mailmap
generated
vendored
Normal file
3
vendor/github.com/spf13/cobra/.mailmap
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Steve Francia <steve.francia@gmail.com>
|
||||||
|
Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
|
||||||
|
Fabiano Franz <ffranz@redhat.com> <contact@fabianofranz.com>
|
||||||
37
vendor/github.com/spf13/cobra/CONDUCT.md
generated
vendored
Normal file
37
vendor/github.com/spf13/cobra/CONDUCT.md
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
## Cobra User Contract
|
||||||
|
|
||||||
|
### Versioning
|
||||||
|
Cobra will follow a steady release cadence. Non breaking changes will be released as minor versions quarterly. Patch bug releases are at the discretion of the maintainers. Users can expect security patch fixes to be released within relatively short order of a CVE becoming known. For more information on security patch fixes see the CVE section below. Releases will follow [Semantic Versioning](https://semver.org/). Users tracking the Master branch should expect unpredictable breaking changes as the project continues to move forward. For stability, it is highly recommended to use a release.
|
||||||
|
|
||||||
|
### Backward Compatibility
|
||||||
|
We will maintain two major releases in a moving window. The N-1 release will only receive bug fixes and security updates and will be dropped once N+1 is released.
|
||||||
|
|
||||||
|
### Deprecation
|
||||||
|
Deprecation of Go versions or dependent packages will only occur in major releases. To reduce the change of this taking users by surprise, any large deprecation will be preceded by an announcement in the [#cobra slack channel](https://gophers.slack.com/archives/CD3LP1199) and an Issue on Github.
|
||||||
|
|
||||||
|
### CVE
|
||||||
|
Maintainers will make every effort to release security patches in the case of a medium to high severity CVE directly impacting the library. The speed in which these patches reach a release is up to the discretion of the maintainers. A low severity CVE may be a lower priority than a high severity one.
|
||||||
|
|
||||||
|
### Communication
|
||||||
|
Cobra maintainers will use GitHub issues and the [#cobra slack channel](https://gophers.slack.com/archives/CD3LP1199) as the primary means of communication with the community. This is to foster open communication with all users and contributors.
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
Breaking changes are generally allowed in the master branch, as this is the branch used to develop the next release of Cobra.
|
||||||
|
|
||||||
|
There may be times, however, when master is closed for breaking changes. This is likely to happen as we near the release of a new version.
|
||||||
|
|
||||||
|
Breaking changes are not allowed in release branches, as these represent minor versions that have already been released. These version have consumers who expect the APIs, behaviors, etc, to remain stable during the lifetime of the patch stream for the minor release.
|
||||||
|
|
||||||
|
Examples of breaking changes include:
|
||||||
|
- Removing or renaming exported constant, variable, type, or function.
|
||||||
|
- Updating the version of critical libraries such as `spf13/pflag`, `spf13/viper` etc...
|
||||||
|
- Some version updates may be acceptable for picking up bug fixes, but maintainers must exercise caution when reviewing.
|
||||||
|
|
||||||
|
There may, at times, need to be exceptions where breaking changes are allowed in release branches. These are at the discretion of the project's maintainers, and must be carefully considered before merging.
|
||||||
|
|
||||||
|
### CI Testing
|
||||||
|
Maintainers will ensure the Cobra test suite utilizes the current supported versions of Golang.
|
||||||
|
|
||||||
|
### Disclaimer
|
||||||
|
Changes to this document and the contents therein are at the discretion of the maintainers.
|
||||||
|
None of the contents of this document are legally binding in any way to the maintainers or the users.
|
||||||
50
vendor/github.com/spf13/cobra/CONTRIBUTING.md
generated
vendored
Normal file
50
vendor/github.com/spf13/cobra/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Contributing to Cobra
|
||||||
|
|
||||||
|
Thank you so much for contributing to Cobra. We appreciate your time and help.
|
||||||
|
Here are some guidelines to help you get started.
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
Be kind and respectful to the members of the community. Take time to educate
|
||||||
|
others who are seeking help. Harassment of any kind will not be tolerated.
|
||||||
|
|
||||||
|
## Questions
|
||||||
|
|
||||||
|
If you have questions regarding Cobra, feel free to ask it in the community
|
||||||
|
[#cobra Slack channel][cobra-slack]
|
||||||
|
|
||||||
|
## Filing a bug or feature
|
||||||
|
|
||||||
|
1. Before filing an issue, please check the existing issues to see if a
|
||||||
|
similar one was already opened. If there is one already opened, feel free
|
||||||
|
to comment on it.
|
||||||
|
1. If you believe you've found a bug, please provide detailed steps of
|
||||||
|
reproduction, the version of Cobra and anything else you believe will be
|
||||||
|
useful to help troubleshoot it (e.g. OS environment, environment variables,
|
||||||
|
etc...). Also state the current behavior vs. the expected behavior.
|
||||||
|
1. If you'd like to see a feature or an enhancement please open an issue with
|
||||||
|
a clear title and description of what the feature is and why it would be
|
||||||
|
beneficial to the project and its users.
|
||||||
|
|
||||||
|
## Submitting changes
|
||||||
|
|
||||||
|
1. CLA: Upon submitting a Pull Request (PR), contributors will be prompted to
|
||||||
|
sign a CLA. Please sign the CLA :slightly_smiling_face:
|
||||||
|
1. Tests: If you are submitting code, please ensure you have adequate tests
|
||||||
|
for the feature. Tests can be run via `go test ./...` or `make test`.
|
||||||
|
1. Since this is golang project, ensure the new code is properly formatted to
|
||||||
|
ensure code consistency. Run `make all`.
|
||||||
|
|
||||||
|
### Quick steps to contribute
|
||||||
|
|
||||||
|
1. Fork the project.
|
||||||
|
1. Download your fork to your PC (`git clone https://github.com/your_username/cobra && cd cobra`)
|
||||||
|
1. Create your feature branch (`git checkout -b my-new-feature`)
|
||||||
|
1. Make changes and run tests (`make test`)
|
||||||
|
1. Add them to staging (`git add .`)
|
||||||
|
1. Commit your changes (`git commit -m 'Add some feature'`)
|
||||||
|
1. Push to the branch (`git push origin my-new-feature`)
|
||||||
|
1. Create new pull request
|
||||||
|
|
||||||
|
<!-- Links -->
|
||||||
|
[cobra-slack]: https://gophers.slack.com/archives/CD3LP1199
|
||||||
174
vendor/github.com/spf13/cobra/LICENSE.txt
generated
vendored
Normal file
174
vendor/github.com/spf13/cobra/LICENSE.txt
generated
vendored
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
13
vendor/github.com/spf13/cobra/MAINTAINERS
generated
vendored
Normal file
13
vendor/github.com/spf13/cobra/MAINTAINERS
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
maintainers:
|
||||||
|
- spf13
|
||||||
|
- johnSchnake
|
||||||
|
- jpmcb
|
||||||
|
- marckhouzam
|
||||||
|
inactive:
|
||||||
|
- anthonyfok
|
||||||
|
- bep
|
||||||
|
- bogem
|
||||||
|
- broady
|
||||||
|
- eparis
|
||||||
|
- jharshman
|
||||||
|
- wfernandes
|
||||||
35
vendor/github.com/spf13/cobra/Makefile
generated
vendored
Normal file
35
vendor/github.com/spf13/cobra/Makefile
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
BIN="./bin"
|
||||||
|
SRC=$(shell find . -name "*.go")
|
||||||
|
|
||||||
|
ifeq (, $(shell which golangci-lint))
|
||||||
|
$(warning "could not find golangci-lint in $(PATH), run: curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh")
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: fmt lint test install_deps clean
|
||||||
|
|
||||||
|
default: all
|
||||||
|
|
||||||
|
all: fmt test
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
$(info ******************** checking formatting ********************)
|
||||||
|
@test -z $(shell gofmt -l $(SRC)) || (gofmt -d $(SRC); exit 1)
|
||||||
|
|
||||||
|
lint:
|
||||||
|
$(info ******************** running lint tools ********************)
|
||||||
|
golangci-lint run -v
|
||||||
|
|
||||||
|
test: install_deps
|
||||||
|
$(info ******************** running tests ********************)
|
||||||
|
go test -v ./...
|
||||||
|
|
||||||
|
richtest: install_deps
|
||||||
|
$(info ******************** running tests with kyoh86/richgo ********************)
|
||||||
|
richgo test -v ./...
|
||||||
|
|
||||||
|
install_deps:
|
||||||
|
$(info ******************** downloading dependencies ********************)
|
||||||
|
go get -v ./...
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BIN)
|
||||||
133
vendor/github.com/spf13/cobra/README.md
generated
vendored
Normal file
133
vendor/github.com/spf13/cobra/README.md
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
<div align="center">
|
||||||
|
<a href="https://cobra.dev">
|
||||||
|
<img width="512" height="535" alt="cobra-logo" src="https://github.com/user-attachments/assets/c8bf9aad-b5ae-41d3-8899-d83baec10af8" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Cobra is a library for creating powerful modern CLI applications.
|
||||||
|
|
||||||
|
<a href="https://cobra.dev">Visit Cobra.dev for extensive documentation</a>
|
||||||
|
|
||||||
|
|
||||||
|
Cobra is used in many Go projects such as [Kubernetes](https://kubernetes.io/),
|
||||||
|
[Hugo](https://gohugo.io), and [GitHub CLI](https://github.com/cli/cli) to
|
||||||
|
name a few. [This list](site/content/projects_using_cobra.md) contains a more extensive list of projects using Cobra.
|
||||||
|
|
||||||
|
[](https://github.com/spf13/cobra/actions?query=workflow%3ATest)
|
||||||
|
[](https://pkg.go.dev/github.com/spf13/cobra)
|
||||||
|
[](https://goreportcard.com/report/github.com/spf13/cobra)
|
||||||
|
[](https://gophers.slack.com/archives/CD3LP1199)
|
||||||
|
<hr>
|
||||||
|
<div align="center" markdown="1">
|
||||||
|
<sup>Supported by:</sup>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<a href="https://www.warp.dev/cobra">
|
||||||
|
<img alt="Warp sponsorship" width="400" src="https://github.com/user-attachments/assets/ab8dd143-b0fd-4904-bdc5-dd7ecac94eae">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
### [Warp, the AI terminal for devs](https://www.warp.dev/cobra)
|
||||||
|
[Try Cobra in Warp today](https://www.warp.dev/cobra)<br>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
Cobra is a library providing a simple interface to create powerful modern CLI
|
||||||
|
interfaces similar to git & go tools.
|
||||||
|
|
||||||
|
Cobra provides:
|
||||||
|
* Easy subcommand-based CLIs: `app server`, `app fetch`, etc.
|
||||||
|
* Fully POSIX-compliant flags (including short & long versions)
|
||||||
|
* Nested subcommands
|
||||||
|
* Global, local and cascading flags
|
||||||
|
* Intelligent suggestions (`app srver`... did you mean `app server`?)
|
||||||
|
* Automatic help generation for commands and flags
|
||||||
|
* Grouping help for subcommands
|
||||||
|
* Automatic help flag recognition of `-h`, `--help`, etc.
|
||||||
|
* Automatically generated shell autocomplete for your application (bash, zsh, fish, powershell)
|
||||||
|
* Automatically generated man pages for your application
|
||||||
|
* Command aliases so you can change things without breaking them
|
||||||
|
* The flexibility to define your own help, usage, etc.
|
||||||
|
* Optional seamless integration with [viper](https://github.com/spf13/viper) for 12-factor apps
|
||||||
|
|
||||||
|
# Concepts
|
||||||
|
|
||||||
|
Cobra is built on a structure of commands, arguments & flags.
|
||||||
|
|
||||||
|
**Commands** represent actions, **Args** are things and **Flags** are modifiers for those actions.
|
||||||
|
|
||||||
|
The best applications read like sentences when used, and as a result, users
|
||||||
|
intuitively know how to interact with them.
|
||||||
|
|
||||||
|
The pattern to follow is
|
||||||
|
`APPNAME VERB NOUN --ADJECTIVE`
|
||||||
|
or
|
||||||
|
`APPNAME COMMAND ARG --FLAG`.
|
||||||
|
|
||||||
|
A few good real world examples may better illustrate this point.
|
||||||
|
|
||||||
|
In the following example, 'server' is a command, and 'port' is a flag:
|
||||||
|
|
||||||
|
hugo server --port=1313
|
||||||
|
|
||||||
|
In this command we are telling Git to clone the url bare.
|
||||||
|
|
||||||
|
git clone URL --bare
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
Command is the central point of the application. Each interaction that
|
||||||
|
the application supports will be contained in a Command. A command can
|
||||||
|
have children commands and optionally run an action.
|
||||||
|
|
||||||
|
In the example above, 'server' is the command.
|
||||||
|
|
||||||
|
[More about cobra.Command](https://pkg.go.dev/github.com/spf13/cobra#Command)
|
||||||
|
|
||||||
|
## Flags
|
||||||
|
|
||||||
|
A flag is a way to modify the behavior of a command. Cobra supports
|
||||||
|
fully POSIX-compliant flags as well as the Go [flag package](https://golang.org/pkg/flag/).
|
||||||
|
A Cobra command can define flags that persist through to children commands
|
||||||
|
and flags that are only available to that command.
|
||||||
|
|
||||||
|
In the example above, 'port' is the flag.
|
||||||
|
|
||||||
|
Flag functionality is provided by the [pflag
|
||||||
|
library](https://github.com/spf13/pflag), a fork of the flag standard library
|
||||||
|
which maintains the same interface while adding POSIX compliance.
|
||||||
|
|
||||||
|
# Installing
|
||||||
|
Using Cobra is easy. First, use `go get` to install the latest version
|
||||||
|
of the library.
|
||||||
|
|
||||||
|
```
|
||||||
|
go get -u github.com/spf13/cobra@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, include Cobra in your application:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/spf13/cobra"
|
||||||
|
```
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
`cobra-cli` is a command line program to generate cobra applications and command files.
|
||||||
|
It will bootstrap your application scaffolding to rapidly
|
||||||
|
develop a Cobra-based application. It is the easiest way to incorporate Cobra into your application.
|
||||||
|
|
||||||
|
It can be installed by running:
|
||||||
|
|
||||||
|
```
|
||||||
|
go install github.com/spf13/cobra-cli@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
For complete details on using the Cobra-CLI generator, please read [The Cobra Generator README](https://github.com/spf13/cobra-cli/blob/main/README.md)
|
||||||
|
|
||||||
|
For complete details on using the Cobra library, please read [The Cobra User Guide](site/content/user_guide.md).
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
Cobra is released under the Apache 2.0 license. See [LICENSE.txt](LICENSE.txt)
|
||||||
105
vendor/github.com/spf13/cobra/SECURITY.md
generated
vendored
Normal file
105
vendor/github.com/spf13/cobra/SECURITY.md
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
The `cobra` maintainers take security issues seriously and
|
||||||
|
we appreciate your efforts to _**responsibly**_ disclose your findings.
|
||||||
|
We will make every effort to swiftly respond and address concerns.
|
||||||
|
|
||||||
|
To report a security vulnerability:
|
||||||
|
|
||||||
|
1. **DO NOT** create a public GitHub issue for the vulnerability!
|
||||||
|
2. **DO NOT** create a public GitHub Pull Request with a fix for the vulnerability!
|
||||||
|
3. Send an email to `cobra-security@googlegroups.com`.
|
||||||
|
4. Include the following details in your report:
|
||||||
|
- Description of the vulnerability
|
||||||
|
- Steps to reproduce
|
||||||
|
- Potential impact of the vulnerability (to your downstream project, to the Go ecosystem, etc.)
|
||||||
|
- Any potential mitigations you've already identified
|
||||||
|
5. Allow up to 7 days for an initial response.
|
||||||
|
You should receive an acknowledgment of your report and an estimated timeline for a fix.
|
||||||
|
6. (Optional) If you have a fix and would like to contribute your patch, please work
|
||||||
|
directly with the maintainers via `cobra-security@googlegroups.com` to
|
||||||
|
coordinate pushing the patch to GitHub, cutting a new release, and disclosing the change.
|
||||||
|
|
||||||
|
## Response Process
|
||||||
|
|
||||||
|
When a security vulnerability report is received, the `cobra` maintainers will:
|
||||||
|
|
||||||
|
1. Confirm receipt of the vulnerability report within 7 days.
|
||||||
|
2. Assess the report to determine if it constitutes a security vulnerability.
|
||||||
|
3. If confirmed, assign the vulnerability a severity level and create a timeline for addressing it.
|
||||||
|
4. Develop and test a fix.
|
||||||
|
5. Patch the vulnerability and make a new GitHub release: the maintainers will coordinate disclosure with the reporter.
|
||||||
|
6. Create a new GitHub Security Advisory to inform the broader Go ecosystem
|
||||||
|
|
||||||
|
## Disclosure Policy
|
||||||
|
|
||||||
|
The `cobra` maintainers follow a coordinated disclosure process:
|
||||||
|
|
||||||
|
1. Security vulnerabilities will be addressed as quickly as possible.
|
||||||
|
2. A CVE (Common Vulnerabilities and Exposures) identifier will be requested for significant vulnerabilities
|
||||||
|
that are within `cobra` itself.
|
||||||
|
3. Once a fix is ready, the maintainers will:
|
||||||
|
- Release a new version containing the fix.
|
||||||
|
- Update the security advisory with details about the vulnerability.
|
||||||
|
- Credit the reporter (unless they wish to remain anonymous).
|
||||||
|
- Credit the fixer (unless they wish to remain anonymous, this may be the same as the reporter).
|
||||||
|
- Announce the vulnerability through appropriate channels
|
||||||
|
(GitHub Security Advisory, mailing lists, GitHub Releases, etc.)
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
Security fixes will typically only be released for the most recent major release.
|
||||||
|
|
||||||
|
## Upstream Security Issues
|
||||||
|
|
||||||
|
`cobra` generally will not accept vulnerability reports that originate in upstream
|
||||||
|
dependencies. I.e., if there is a problem in Go code that `cobra` depends on,
|
||||||
|
it is best to engage that project's maintainers and owners.
|
||||||
|
|
||||||
|
This security policy primarily pertains only to `cobra` itself but if you believe you've
|
||||||
|
identified a problem that originates in an upstream dependency and is being widely
|
||||||
|
distributed by `cobra`, please follow the disclosure procedure above: the `cobra`
|
||||||
|
maintainers will work with you to determine the severity and ecosystem impact.
|
||||||
|
|
||||||
|
## Security Updates and CVEs
|
||||||
|
|
||||||
|
Information about known security vulnerabilities and CVEs affecting `cobra` will
|
||||||
|
be published as GitHub Security Advisories at
|
||||||
|
https://github.com/spf13/cobra/security/advisories.
|
||||||
|
|
||||||
|
All users are encouraged to watch the repository and upgrade promptly when
|
||||||
|
security releases are published.
|
||||||
|
|
||||||
|
## `cobra` Security Best Practices for Users
|
||||||
|
|
||||||
|
When using `cobra` in your CLIs, the `cobra` maintainers recommend the following:
|
||||||
|
|
||||||
|
1. Always use the latest version of `cobra`.
|
||||||
|
2. [Use Go modules](https://go.dev/blog/using-go-modules) for dependency management.
|
||||||
|
3. Always use the latest possible version of Go.
|
||||||
|
|
||||||
|
## Security Best Practices for Contributors
|
||||||
|
|
||||||
|
When contributing to `cobra`:
|
||||||
|
|
||||||
|
1. Be mindful of security implications when adding new features or modifying existing ones.
|
||||||
|
2. Be aware of `cobra`'s extremely large reach: it is used in nearly every Go CLI
|
||||||
|
(like Kubernetes, Docker, Prometheus, etc. etc.)
|
||||||
|
3. Write tests that explicitly cover edge cases and potential issues.
|
||||||
|
4. If you discover a security issue while working on `cobra`, please report it
|
||||||
|
following the process above rather than opening a public pull request or issue that
|
||||||
|
addresses the vulnerability.
|
||||||
|
5. Take personal sec-ops seriously and secure your GitHub account: use [two-factor authentication](https://docs.github.com/en/authentication/securing-your-account-with-two-factor-authentication-2fa),
|
||||||
|
[sign your commits with a GPG or SSH key](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification),
|
||||||
|
etc.
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
The `cobra` maintainers would like to thank all security researchers and
|
||||||
|
community members who help keep cobra, its users, and the entire Go ecosystem secure through responsible disclosures!!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This security policy is inspired by the [Open Web Application Security Project (OWASP)](https://owasp.org/) guidelines and security best practices.*
|
||||||
60
vendor/github.com/spf13/cobra/active_help.go
generated
vendored
Normal file
60
vendor/github.com/spf13/cobra/active_help.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2013-2023 The Cobra Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
activeHelpMarker = "_activeHelp_ "
|
||||||
|
// The below values should not be changed: programs will be using them explicitly
|
||||||
|
// in their user documentation, and users will be using them explicitly.
|
||||||
|
activeHelpEnvVarSuffix = "ACTIVE_HELP"
|
||||||
|
activeHelpGlobalEnvVar = configEnvVarGlobalPrefix + "_" + activeHelpEnvVarSuffix
|
||||||
|
activeHelpGlobalDisable = "0"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AppendActiveHelp adds the specified string to the specified array to be used as ActiveHelp.
|
||||||
|
// Such strings will be processed by the completion script and will be shown as ActiveHelp
|
||||||
|
// to the user.
|
||||||
|
// The array parameter should be the array that will contain the completions.
|
||||||
|
// This function can be called multiple times before and/or after completions are added to
|
||||||
|
// the array. Each time this function is called with the same array, the new
|
||||||
|
// ActiveHelp line will be shown below the previous ones when completion is triggered.
|
||||||
|
func AppendActiveHelp(compArray []Completion, activeHelpStr string) []Completion {
|
||||||
|
return append(compArray, fmt.Sprintf("%s%s", activeHelpMarker, activeHelpStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActiveHelpConfig returns the value of the ActiveHelp environment variable
|
||||||
|
// <PROGRAM>_ACTIVE_HELP where <PROGRAM> is the name of the root command in upper
|
||||||
|
// case, with all non-ASCII-alphanumeric characters replaced by `_`.
|
||||||
|
// It will always return "0" if the global environment variable COBRA_ACTIVE_HELP
|
||||||
|
// is set to "0".
|
||||||
|
func GetActiveHelpConfig(cmd *Command) string {
|
||||||
|
activeHelpCfg := os.Getenv(activeHelpGlobalEnvVar)
|
||||||
|
if activeHelpCfg != activeHelpGlobalDisable {
|
||||||
|
activeHelpCfg = os.Getenv(activeHelpEnvVar(cmd.Root().Name()))
|
||||||
|
}
|
||||||
|
return activeHelpCfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// activeHelpEnvVar returns the name of the program-specific ActiveHelp environment
|
||||||
|
// variable. It has the format <PROGRAM>_ACTIVE_HELP where <PROGRAM> is the name of the
|
||||||
|
// root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`.
|
||||||
|
func activeHelpEnvVar(name string) string {
|
||||||
|
return configEnvVar(name, activeHelpEnvVarSuffix)
|
||||||
|
}
|
||||||
131
vendor/github.com/spf13/cobra/args.go
generated
vendored
Normal file
131
vendor/github.com/spf13/cobra/args.go
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
// Copyright 2013-2023 The Cobra Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PositionalArgs func(cmd *Command, args []string) error
|
||||||
|
|
||||||
|
// legacyArgs validation has the following behaviour:
|
||||||
|
// - root commands with no subcommands can take arbitrary arguments
|
||||||
|
// - root commands with subcommands will do subcommand validity checking
|
||||||
|
// - subcommands will always accept arbitrary arguments
|
||||||
|
func legacyArgs(cmd *Command, args []string) error {
|
||||||
|
// no subcommand, always take args
|
||||||
|
if !cmd.HasSubCommands() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// root command with subcommands, do subcommand checking.
|
||||||
|
if !cmd.HasParent() && len(args) > 0 {
|
||||||
|
return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.findSuggestions(args[0]))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoArgs returns an error if any args are included.
|
||||||
|
func NoArgs(cmd *Command, args []string) error {
|
||||||
|
if len(args) > 0 {
|
||||||
|
return fmt.Errorf("unknown command %q for %q", args[0], cmd.CommandPath())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyValidArgs returns an error if there are any positional args that are not in
|
||||||
|
// the `ValidArgs` field of `Command`
|
||||||
|
func OnlyValidArgs(cmd *Command, args []string) error {
|
||||||
|
if len(cmd.ValidArgs) > 0 {
|
||||||
|
// Remove any description that may be included in ValidArgs.
|
||||||
|
// A description is following a tab character.
|
||||||
|
validArgs := make([]string, 0, len(cmd.ValidArgs))
|
||||||
|
for _, v := range cmd.ValidArgs {
|
||||||
|
validArgs = append(validArgs, strings.SplitN(v, "\t", 2)[0])
|
||||||
|
}
|
||||||
|
for _, v := range args {
|
||||||
|
if !stringInSlice(v, validArgs) {
|
||||||
|
return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.findSuggestions(args[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArbitraryArgs never returns an error.
|
||||||
|
func ArbitraryArgs(cmd *Command, args []string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinimumNArgs returns an error if there is not at least N args.
|
||||||
|
func MinimumNArgs(n int) PositionalArgs {
|
||||||
|
return func(cmd *Command, args []string) error {
|
||||||
|
if len(args) < n {
|
||||||
|
return fmt.Errorf("requires at least %d arg(s), only received %d", n, len(args))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaximumNArgs returns an error if there are more than N args.
|
||||||
|
func MaximumNArgs(n int) PositionalArgs {
|
||||||
|
return func(cmd *Command, args []string) error {
|
||||||
|
if len(args) > n {
|
||||||
|
return fmt.Errorf("accepts at most %d arg(s), received %d", n, len(args))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExactArgs returns an error if there are not exactly n args.
|
||||||
|
func ExactArgs(n int) PositionalArgs {
|
||||||
|
return func(cmd *Command, args []string) error {
|
||||||
|
if len(args) != n {
|
||||||
|
return fmt.Errorf("accepts %d arg(s), received %d", n, len(args))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RangeArgs returns an error if the number of args is not within the expected range.
|
||||||
|
func RangeArgs(min int, max int) PositionalArgs {
|
||||||
|
return func(cmd *Command, args []string) error {
|
||||||
|
if len(args) < min || len(args) > max {
|
||||||
|
return fmt.Errorf("accepts between %d and %d arg(s), received %d", min, max, len(args))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchAll allows combining several PositionalArgs to work in concert.
|
||||||
|
func MatchAll(pargs ...PositionalArgs) PositionalArgs {
|
||||||
|
return func(cmd *Command, args []string) error {
|
||||||
|
for _, parg := range pargs {
|
||||||
|
if err := parg(cmd, args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExactValidArgs returns an error if there are not exactly N positional args OR
|
||||||
|
// there are any positional args that are not in the `ValidArgs` field of `Command`
|
||||||
|
//
|
||||||
|
// Deprecated: use MatchAll(ExactArgs(n), OnlyValidArgs) instead
|
||||||
|
func ExactValidArgs(n int) PositionalArgs {
|
||||||
|
return MatchAll(ExactArgs(n), OnlyValidArgs)
|
||||||
|
}
|
||||||
709
vendor/github.com/spf13/cobra/bash_completions.go
generated
vendored
Normal file
709
vendor/github.com/spf13/cobra/bash_completions.go
generated
vendored
Normal file
@@ -0,0 +1,709 @@
|
|||||||
|
// Copyright 2013-2023 The Cobra Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Annotations for Bash completion.
|
||||||
|
const (
|
||||||
|
BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions"
|
||||||
|
BashCompCustom = "cobra_annotation_bash_completion_custom"
|
||||||
|
BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
|
||||||
|
BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writePreamble(buf io.StringWriter, name string) {
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name))
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(`
|
||||||
|
__%[1]s_debug()
|
||||||
|
{
|
||||||
|
if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then
|
||||||
|
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Homebrew on Macs have version 1.3 of bash-completion which doesn't include
|
||||||
|
# _init_completion. This is a very minimal version of that function.
|
||||||
|
__%[1]s_init_completion()
|
||||||
|
{
|
||||||
|
COMPREPLY=()
|
||||||
|
_get_comp_words_by_ref "$@" cur prev words cword
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_index_of_word()
|
||||||
|
{
|
||||||
|
local w word=$1
|
||||||
|
shift
|
||||||
|
index=0
|
||||||
|
for w in "$@"; do
|
||||||
|
[[ $w = "$word" ]] && return
|
||||||
|
index=$((index+1))
|
||||||
|
done
|
||||||
|
index=-1
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_contains_word()
|
||||||
|
{
|
||||||
|
local w word=$1; shift
|
||||||
|
for w in "$@"; do
|
||||||
|
[[ $w = "$word" ]] && return
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_handle_go_custom_completion()
|
||||||
|
{
|
||||||
|
__%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}"
|
||||||
|
|
||||||
|
local shellCompDirectiveError=%[3]d
|
||||||
|
local shellCompDirectiveNoSpace=%[4]d
|
||||||
|
local shellCompDirectiveNoFileComp=%[5]d
|
||||||
|
local shellCompDirectiveFilterFileExt=%[6]d
|
||||||
|
local shellCompDirectiveFilterDirs=%[7]d
|
||||||
|
|
||||||
|
local out requestComp lastParam lastChar comp directive args
|
||||||
|
|
||||||
|
# Prepare the command to request completions for the program.
|
||||||
|
# Calling ${words[0]} instead of directly %[1]s allows handling aliases
|
||||||
|
args=("${words[@]:1}")
|
||||||
|
# Disable ActiveHelp which is not supported for bash completion v1
|
||||||
|
requestComp="%[8]s=0 ${words[0]} %[2]s ${args[*]}"
|
||||||
|
|
||||||
|
lastParam=${words[$((${#words[@]}-1))]}
|
||||||
|
lastChar=${lastParam:$((${#lastParam}-1)):1}
|
||||||
|
__%[1]s_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}"
|
||||||
|
|
||||||
|
if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then
|
||||||
|
# If the last parameter is complete (there is a space following it)
|
||||||
|
# We add an extra empty parameter so we can indicate this to the go method.
|
||||||
|
__%[1]s_debug "${FUNCNAME[0]}: Adding extra empty parameter"
|
||||||
|
requestComp="${requestComp} \"\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
__%[1]s_debug "${FUNCNAME[0]}: calling ${requestComp}"
|
||||||
|
# Use eval to handle any environment variables and such
|
||||||
|
out=$(eval "${requestComp}" 2>/dev/null)
|
||||||
|
|
||||||
|
# Extract the directive integer at the very end of the output following a colon (:)
|
||||||
|
directive=${out##*:}
|
||||||
|
# Remove the directive
|
||||||
|
out=${out%%:*}
|
||||||
|
if [ "${directive}" = "${out}" ]; then
|
||||||
|
# There is not directive specified
|
||||||
|
directive=0
|
||||||
|
fi
|
||||||
|
__%[1]s_debug "${FUNCNAME[0]}: the completion directive is: ${directive}"
|
||||||
|
__%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out}"
|
||||||
|
|
||||||
|
if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
|
||||||
|
# Error code. No completion.
|
||||||
|
__%[1]s_debug "${FUNCNAME[0]}: received error from custom completion go code"
|
||||||
|
return
|
||||||
|
else
|
||||||
|
if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
|
||||||
|
if [[ $(type -t compopt) = "builtin" ]]; then
|
||||||
|
__%[1]s_debug "${FUNCNAME[0]}: activating no space"
|
||||||
|
compopt -o nospace
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
|
||||||
|
if [[ $(type -t compopt) = "builtin" ]]; then
|
||||||
|
__%[1]s_debug "${FUNCNAME[0]}: activating no file completion"
|
||||||
|
compopt +o default
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
|
||||||
|
# File extension filtering
|
||||||
|
local fullFilter filter filteringCmd
|
||||||
|
# Do not use quotes around the $out variable or else newline
|
||||||
|
# characters will be kept.
|
||||||
|
for filter in ${out}; do
|
||||||
|
fullFilter+="$filter|"
|
||||||
|
done
|
||||||
|
|
||||||
|
filteringCmd="_filedir $fullFilter"
|
||||||
|
__%[1]s_debug "File filtering command: $filteringCmd"
|
||||||
|
$filteringCmd
|
||||||
|
elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
|
||||||
|
# File completion for directories only
|
||||||
|
local subdir
|
||||||
|
# Use printf to strip any trailing newline
|
||||||
|
subdir=$(printf "%%s" "${out}")
|
||||||
|
if [ -n "$subdir" ]; then
|
||||||
|
__%[1]s_debug "Listing directories in $subdir"
|
||||||
|
__%[1]s_handle_subdirs_in_dir_flag "$subdir"
|
||||||
|
else
|
||||||
|
__%[1]s_debug "Listing directories in ."
|
||||||
|
_filedir -d
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
while IFS='' read -r comp; do
|
||||||
|
COMPREPLY+=("$comp")
|
||||||
|
done < <(compgen -W "${out}" -- "$cur")
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_handle_reply()
|
||||||
|
{
|
||||||
|
__%[1]s_debug "${FUNCNAME[0]}"
|
||||||
|
local comp
|
||||||
|
case $cur in
|
||||||
|
-*)
|
||||||
|
if [[ $(type -t compopt) = "builtin" ]]; then
|
||||||
|
compopt -o nospace
|
||||||
|
fi
|
||||||
|
local allflags
|
||||||
|
if [ ${#must_have_one_flag[@]} -ne 0 ]; then
|
||||||
|
allflags=("${must_have_one_flag[@]}")
|
||||||
|
else
|
||||||
|
allflags=("${flags[*]} ${two_word_flags[*]}")
|
||||||
|
fi
|
||||||
|
while IFS='' read -r comp; do
|
||||||
|
COMPREPLY+=("$comp")
|
||||||
|
done < <(compgen -W "${allflags[*]}" -- "$cur")
|
||||||
|
if [[ $(type -t compopt) = "builtin" ]]; then
|
||||||
|
[[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace
|
||||||
|
fi
|
||||||
|
|
||||||
|
# complete after --flag=abc
|
||||||
|
if [[ $cur == *=* ]]; then
|
||||||
|
if [[ $(type -t compopt) = "builtin" ]]; then
|
||||||
|
compopt +o nospace
|
||||||
|
fi
|
||||||
|
|
||||||
|
local index flag
|
||||||
|
flag="${cur%%=*}"
|
||||||
|
__%[1]s_index_of_word "${flag}" "${flags_with_completion[@]}"
|
||||||
|
COMPREPLY=()
|
||||||
|
if [[ ${index} -ge 0 ]]; then
|
||||||
|
PREFIX=""
|
||||||
|
cur="${cur#*=}"
|
||||||
|
${flags_completion[${index}]}
|
||||||
|
if [ -n "${ZSH_VERSION:-}" ]; then
|
||||||
|
# zsh completion needs --flag= prefix
|
||||||
|
eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${flag_parsing_disabled}" ]]; then
|
||||||
|
# If flag parsing is enabled, we have completed the flags and can return.
|
||||||
|
# If flag parsing is disabled, we may not know all (or any) of the flags, so we fallthrough
|
||||||
|
# to possibly call handle_go_custom_completion.
|
||||||
|
return 0;
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# check if we are handling a flag with special work handling
|
||||||
|
local index
|
||||||
|
__%[1]s_index_of_word "${prev}" "${flags_with_completion[@]}"
|
||||||
|
if [[ ${index} -ge 0 ]]; then
|
||||||
|
${flags_completion[${index}]}
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# we are parsing a flag and don't have a special handler, no completion
|
||||||
|
if [[ ${cur} != "${words[cword]}" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local completions
|
||||||
|
completions=("${commands[@]}")
|
||||||
|
if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then
|
||||||
|
completions+=("${must_have_one_noun[@]}")
|
||||||
|
elif [[ -n "${has_completion_function}" ]]; then
|
||||||
|
# if a go completion function is provided, defer to that function
|
||||||
|
__%[1]s_handle_go_custom_completion
|
||||||
|
fi
|
||||||
|
if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then
|
||||||
|
completions+=("${must_have_one_flag[@]}")
|
||||||
|
fi
|
||||||
|
while IFS='' read -r comp; do
|
||||||
|
COMPREPLY+=("$comp")
|
||||||
|
done < <(compgen -W "${completions[*]}" -- "$cur")
|
||||||
|
|
||||||
|
if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then
|
||||||
|
while IFS='' read -r comp; do
|
||||||
|
COMPREPLY+=("$comp")
|
||||||
|
done < <(compgen -W "${noun_aliases[*]}" -- "$cur")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#COMPREPLY[@]} -eq 0 ]]; then
|
||||||
|
if declare -F __%[1]s_custom_func >/dev/null; then
|
||||||
|
# try command name qualified custom func
|
||||||
|
__%[1]s_custom_func
|
||||||
|
else
|
||||||
|
# otherwise fall back to unqualified for compatibility
|
||||||
|
declare -F __custom_func >/dev/null && __custom_func
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# available in bash-completion >= 2, not always present on macOS
|
||||||
|
if declare -F __ltrim_colon_completions >/dev/null; then
|
||||||
|
__ltrim_colon_completions "$cur"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If there is only 1 completion and it is a flag with an = it will be completed
|
||||||
|
# but we don't want a space after the =
|
||||||
|
if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then
|
||||||
|
compopt -o nospace
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# The arguments should be in the form "ext1|ext2|extn"
|
||||||
|
__%[1]s_handle_filename_extension_flag()
|
||||||
|
{
|
||||||
|
local ext="$1"
|
||||||
|
_filedir "@(${ext})"
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_handle_subdirs_in_dir_flag()
|
||||||
|
{
|
||||||
|
local dir="$1"
|
||||||
|
pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_handle_flag()
|
||||||
|
{
|
||||||
|
__%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
|
||||||
|
|
||||||
|
# if a command required a flag, and we found it, unset must_have_one_flag()
|
||||||
|
local flagname=${words[c]}
|
||||||
|
local flagvalue=""
|
||||||
|
# if the word contained an =
|
||||||
|
if [[ ${words[c]} == *"="* ]]; then
|
||||||
|
flagvalue=${flagname#*=} # take in as flagvalue after the =
|
||||||
|
flagname=${flagname%%=*} # strip everything after the =
|
||||||
|
flagname="${flagname}=" # but put the = back
|
||||||
|
fi
|
||||||
|
__%[1]s_debug "${FUNCNAME[0]}: looking for ${flagname}"
|
||||||
|
if __%[1]s_contains_word "${flagname}" "${must_have_one_flag[@]}"; then
|
||||||
|
must_have_one_flag=()
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if you set a flag which only applies to this command, don't show subcommands
|
||||||
|
if __%[1]s_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then
|
||||||
|
commands=()
|
||||||
|
fi
|
||||||
|
|
||||||
|
# keep flag value with flagname as flaghash
|
||||||
|
# flaghash variable is an associative array which is only supported in bash > 3.
|
||||||
|
if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then
|
||||||
|
if [ -n "${flagvalue}" ] ; then
|
||||||
|
flaghash[${flagname}]=${flagvalue}
|
||||||
|
elif [ -n "${words[ $((c+1)) ]}" ] ; then
|
||||||
|
flaghash[${flagname}]=${words[ $((c+1)) ]}
|
||||||
|
else
|
||||||
|
flaghash[${flagname}]="true" # pad "true" for bool flag
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# skip the argument to a two word flag
|
||||||
|
if [[ ${words[c]} != *"="* ]] && __%[1]s_contains_word "${words[c]}" "${two_word_flags[@]}"; then
|
||||||
|
__%[1]s_debug "${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument"
|
||||||
|
c=$((c+1))
|
||||||
|
# if we are looking for a flags value, don't show commands
|
||||||
|
if [[ $c -eq $cword ]]; then
|
||||||
|
commands=()
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
c=$((c+1))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_handle_noun()
|
||||||
|
{
|
||||||
|
__%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
|
||||||
|
|
||||||
|
if __%[1]s_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then
|
||||||
|
must_have_one_noun=()
|
||||||
|
elif __%[1]s_contains_word "${words[c]}" "${noun_aliases[@]}"; then
|
||||||
|
must_have_one_noun=()
|
||||||
|
fi
|
||||||
|
|
||||||
|
nouns+=("${words[c]}")
|
||||||
|
c=$((c+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_handle_command()
|
||||||
|
{
|
||||||
|
__%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
|
||||||
|
|
||||||
|
local next_command
|
||||||
|
if [[ -n ${last_command} ]]; then
|
||||||
|
next_command="_${last_command}_${words[c]//:/__}"
|
||||||
|
else
|
||||||
|
if [[ $c -eq 0 ]]; then
|
||||||
|
next_command="_%[1]s_root_command"
|
||||||
|
else
|
||||||
|
next_command="_${words[c]//:/__}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
c=$((c+1))
|
||||||
|
__%[1]s_debug "${FUNCNAME[0]}: looking for ${next_command}"
|
||||||
|
declare -F "$next_command" >/dev/null && $next_command
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_handle_word()
|
||||||
|
{
|
||||||
|
if [[ $c -ge $cword ]]; then
|
||||||
|
__%[1]s_handle_reply
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
__%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
|
||||||
|
if [[ "${words[c]}" == -* ]]; then
|
||||||
|
__%[1]s_handle_flag
|
||||||
|
elif __%[1]s_contains_word "${words[c]}" "${commands[@]}"; then
|
||||||
|
__%[1]s_handle_command
|
||||||
|
elif [[ $c -eq 0 ]]; then
|
||||||
|
__%[1]s_handle_command
|
||||||
|
elif __%[1]s_contains_word "${words[c]}" "${command_aliases[@]}"; then
|
||||||
|
# aliashash variable is an associative array which is only supported in bash > 3.
|
||||||
|
if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then
|
||||||
|
words[c]=${aliashash[${words[c]}]}
|
||||||
|
__%[1]s_handle_command
|
||||||
|
else
|
||||||
|
__%[1]s_handle_noun
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
__%[1]s_handle_noun
|
||||||
|
fi
|
||||||
|
__%[1]s_handle_word
|
||||||
|
}
|
||||||
|
|
||||||
|
`, name, ShellCompNoDescRequestCmd,
|
||||||
|
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
||||||
|
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePostscript(buf io.StringWriter, name string) {
|
||||||
|
name = strings.ReplaceAll(name, ":", "__")
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf("__start_%s()\n", name))
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(`{
|
||||||
|
local cur prev words cword split
|
||||||
|
declare -A flaghash 2>/dev/null || :
|
||||||
|
declare -A aliashash 2>/dev/null || :
|
||||||
|
if declare -F _init_completion >/dev/null 2>&1; then
|
||||||
|
_init_completion -s || return
|
||||||
|
else
|
||||||
|
__%[1]s_init_completion -n "=" || return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local c=0
|
||||||
|
local flag_parsing_disabled=
|
||||||
|
local flags=()
|
||||||
|
local two_word_flags=()
|
||||||
|
local local_nonpersistent_flags=()
|
||||||
|
local flags_with_completion=()
|
||||||
|
local flags_completion=()
|
||||||
|
local commands=("%[1]s")
|
||||||
|
local command_aliases=()
|
||||||
|
local must_have_one_flag=()
|
||||||
|
local must_have_one_noun=()
|
||||||
|
local has_completion_function=""
|
||||||
|
local last_command=""
|
||||||
|
local nouns=()
|
||||||
|
local noun_aliases=()
|
||||||
|
|
||||||
|
__%[1]s_handle_word
|
||||||
|
}
|
||||||
|
|
||||||
|
`, name))
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then
|
||||||
|
complete -o default -F __start_%s %s
|
||||||
|
else
|
||||||
|
complete -o default -o nospace -F __start_%s %s
|
||||||
|
fi
|
||||||
|
|
||||||
|
`, name, name, name, name))
|
||||||
|
WriteStringAndCheck(buf, "# ex: ts=4 sw=4 et filetype=sh\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeCommands(buf io.StringWriter, cmd *Command) {
|
||||||
|
WriteStringAndCheck(buf, " commands=()\n")
|
||||||
|
for _, c := range cmd.Commands() {
|
||||||
|
if !c.IsAvailableCommand() && c != cmd.helpCommand {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(" commands+=(%q)\n", c.Name()))
|
||||||
|
writeCmdAliases(buf, c)
|
||||||
|
}
|
||||||
|
WriteStringAndCheck(buf, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFlagHandler(buf io.StringWriter, name string, annotations map[string][]string, cmd *Command) {
|
||||||
|
for key, value := range annotations {
|
||||||
|
switch key {
|
||||||
|
case BashCompFilenameExt:
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
|
||||||
|
|
||||||
|
var ext string
|
||||||
|
if len(value) > 0 {
|
||||||
|
ext = fmt.Sprintf("__%s_handle_filename_extension_flag ", cmd.Root().Name()) + strings.Join(value, "|")
|
||||||
|
} else {
|
||||||
|
ext = "_filedir"
|
||||||
|
}
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(" flags_completion+=(%q)\n", ext))
|
||||||
|
case BashCompCustom:
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
|
||||||
|
|
||||||
|
if len(value) > 0 {
|
||||||
|
handlers := strings.Join(value, "; ")
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(" flags_completion+=(%q)\n", handlers))
|
||||||
|
} else {
|
||||||
|
WriteStringAndCheck(buf, " flags_completion+=(:)\n")
|
||||||
|
}
|
||||||
|
case BashCompSubdirsInDir:
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
|
||||||
|
|
||||||
|
var ext string
|
||||||
|
if len(value) == 1 {
|
||||||
|
ext = fmt.Sprintf("__%s_handle_subdirs_in_dir_flag ", cmd.Root().Name()) + value[0]
|
||||||
|
} else {
|
||||||
|
ext = "_filedir -d"
|
||||||
|
}
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(" flags_completion+=(%q)\n", ext))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cbn = "\")\n"
|
||||||
|
|
||||||
|
func writeShortFlag(buf io.StringWriter, flag *pflag.Flag, cmd *Command) {
|
||||||
|
name := flag.Shorthand
|
||||||
|
format := " "
|
||||||
|
if len(flag.NoOptDefVal) == 0 {
|
||||||
|
format += "two_word_"
|
||||||
|
}
|
||||||
|
format += "flags+=(\"-%s" + cbn
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(format, name))
|
||||||
|
writeFlagHandler(buf, "-"+name, flag.Annotations, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFlag(buf io.StringWriter, flag *pflag.Flag, cmd *Command) {
|
||||||
|
name := flag.Name
|
||||||
|
format := " flags+=(\"--%s"
|
||||||
|
if len(flag.NoOptDefVal) == 0 {
|
||||||
|
format += "="
|
||||||
|
}
|
||||||
|
format += cbn
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(format, name))
|
||||||
|
if len(flag.NoOptDefVal) == 0 {
|
||||||
|
format = " two_word_flags+=(\"--%s" + cbn
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(format, name))
|
||||||
|
}
|
||||||
|
writeFlagHandler(buf, "--"+name, flag.Annotations, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeLocalNonPersistentFlag(buf io.StringWriter, flag *pflag.Flag) {
|
||||||
|
name := flag.Name
|
||||||
|
format := " local_nonpersistent_flags+=(\"--%[1]s" + cbn
|
||||||
|
if len(flag.NoOptDefVal) == 0 {
|
||||||
|
format += " local_nonpersistent_flags+=(\"--%[1]s=" + cbn
|
||||||
|
}
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(format, name))
|
||||||
|
if len(flag.Shorthand) > 0 {
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(" local_nonpersistent_flags+=(\"-%s\")\n", flag.Shorthand))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareCustomAnnotationsForFlags setup annotations for go completions for registered flags
|
||||||
|
func prepareCustomAnnotationsForFlags(cmd *Command) {
|
||||||
|
flagCompletionMutex.RLock()
|
||||||
|
defer flagCompletionMutex.RUnlock()
|
||||||
|
for flag := range flagCompletionFunctions {
|
||||||
|
// Make sure the completion script calls the __*_go_custom_completion function for
|
||||||
|
// every registered flag. We need to do this here (and not when the flag was registered
|
||||||
|
// for completion) so that we can know the root command name for the prefix
|
||||||
|
// of __<prefix>_go_custom_completion
|
||||||
|
if flag.Annotations == nil {
|
||||||
|
flag.Annotations = map[string][]string{}
|
||||||
|
}
|
||||||
|
flag.Annotations[BashCompCustom] = []string{fmt.Sprintf("__%[1]s_handle_go_custom_completion", cmd.Root().Name())}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFlags(buf io.StringWriter, cmd *Command) {
|
||||||
|
prepareCustomAnnotationsForFlags(cmd)
|
||||||
|
WriteStringAndCheck(buf, ` flags=()
|
||||||
|
two_word_flags=()
|
||||||
|
local_nonpersistent_flags=()
|
||||||
|
flags_with_completion=()
|
||||||
|
flags_completion=()
|
||||||
|
|
||||||
|
`)
|
||||||
|
|
||||||
|
if cmd.DisableFlagParsing {
|
||||||
|
WriteStringAndCheck(buf, " flag_parsing_disabled=1\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
localNonPersistentFlags := cmd.LocalNonPersistentFlags()
|
||||||
|
cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
||||||
|
if nonCompletableFlag(flag) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeFlag(buf, flag, cmd)
|
||||||
|
if len(flag.Shorthand) > 0 {
|
||||||
|
writeShortFlag(buf, flag, cmd)
|
||||||
|
}
|
||||||
|
// localNonPersistentFlags are used to stop the completion of subcommands when one is set
|
||||||
|
// if TraverseChildren is true we should allow to complete subcommands
|
||||||
|
if localNonPersistentFlags.Lookup(flag.Name) != nil && !cmd.Root().TraverseChildren {
|
||||||
|
writeLocalNonPersistentFlag(buf, flag)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
||||||
|
if nonCompletableFlag(flag) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeFlag(buf, flag, cmd)
|
||||||
|
if len(flag.Shorthand) > 0 {
|
||||||
|
writeShortFlag(buf, flag, cmd)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
WriteStringAndCheck(buf, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeRequiredFlag(buf io.StringWriter, cmd *Command) {
|
||||||
|
WriteStringAndCheck(buf, " must_have_one_flag=()\n")
|
||||||
|
flags := cmd.NonInheritedFlags()
|
||||||
|
flags.VisitAll(func(flag *pflag.Flag) {
|
||||||
|
if nonCompletableFlag(flag) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := flag.Annotations[BashCompOneRequiredFlag]; ok {
|
||||||
|
format := " must_have_one_flag+=(\"--%s"
|
||||||
|
if flag.Value.Type() != "bool" {
|
||||||
|
format += "="
|
||||||
|
}
|
||||||
|
format += cbn
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(format, flag.Name))
|
||||||
|
|
||||||
|
if len(flag.Shorthand) > 0 {
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(" must_have_one_flag+=(\"-%s"+cbn, flag.Shorthand))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeRequiredNouns(buf io.StringWriter, cmd *Command) {
|
||||||
|
WriteStringAndCheck(buf, " must_have_one_noun=()\n")
|
||||||
|
sort.Strings(cmd.ValidArgs)
|
||||||
|
for _, value := range cmd.ValidArgs {
|
||||||
|
// Remove any description that may be included following a tab character.
|
||||||
|
// Descriptions are not supported by bash completion.
|
||||||
|
value = strings.SplitN(value, "\t", 2)[0]
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(" must_have_one_noun+=(%q)\n", value))
|
||||||
|
}
|
||||||
|
if cmd.ValidArgsFunction != nil {
|
||||||
|
WriteStringAndCheck(buf, " has_completion_function=1\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeCmdAliases(buf io.StringWriter, cmd *Command) {
|
||||||
|
if len(cmd.Aliases) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(cmd.Aliases)
|
||||||
|
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprint(` if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then`, "\n"))
|
||||||
|
for _, value := range cmd.Aliases {
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(" command_aliases+=(%q)\n", value))
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(" aliashash[%q]=%q\n", value, cmd.Name()))
|
||||||
|
}
|
||||||
|
WriteStringAndCheck(buf, ` fi`)
|
||||||
|
WriteStringAndCheck(buf, "\n")
|
||||||
|
}
|
||||||
|
func writeArgAliases(buf io.StringWriter, cmd *Command) {
|
||||||
|
WriteStringAndCheck(buf, " noun_aliases=()\n")
|
||||||
|
sort.Strings(cmd.ArgAliases)
|
||||||
|
for _, value := range cmd.ArgAliases {
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(" noun_aliases+=(%q)\n", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func gen(buf io.StringWriter, cmd *Command) {
|
||||||
|
for _, c := range cmd.Commands() {
|
||||||
|
if !c.IsAvailableCommand() && c != cmd.helpCommand {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gen(buf, c)
|
||||||
|
}
|
||||||
|
commandName := cmd.CommandPath()
|
||||||
|
commandName = strings.ReplaceAll(commandName, " ", "_")
|
||||||
|
commandName = strings.ReplaceAll(commandName, ":", "__")
|
||||||
|
|
||||||
|
if cmd.Root() == cmd {
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf("_%s_root_command()\n{\n", commandName))
|
||||||
|
} else {
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf("_%s()\n{\n", commandName))
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(" last_command=%q\n", commandName))
|
||||||
|
WriteStringAndCheck(buf, "\n")
|
||||||
|
WriteStringAndCheck(buf, " command_aliases=()\n")
|
||||||
|
WriteStringAndCheck(buf, "\n")
|
||||||
|
|
||||||
|
writeCommands(buf, cmd)
|
||||||
|
writeFlags(buf, cmd)
|
||||||
|
writeRequiredFlag(buf, cmd)
|
||||||
|
writeRequiredNouns(buf, cmd)
|
||||||
|
writeArgAliases(buf, cmd)
|
||||||
|
WriteStringAndCheck(buf, "}\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenBashCompletion generates bash completion file and writes to the passed writer.
|
||||||
|
func (c *Command) GenBashCompletion(w io.Writer) error {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
writePreamble(buf, c.Name())
|
||||||
|
if len(c.BashCompletionFunction) > 0 {
|
||||||
|
buf.WriteString(c.BashCompletionFunction + "\n")
|
||||||
|
}
|
||||||
|
gen(buf, c)
|
||||||
|
writePostscript(buf, c.Name())
|
||||||
|
|
||||||
|
_, err := buf.WriteTo(w)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func nonCompletableFlag(flag *pflag.Flag) bool {
|
||||||
|
return flag.Hidden || len(flag.Deprecated) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenBashCompletionFile generates bash completion file.
|
||||||
|
func (c *Command) GenBashCompletionFile(filename string) error {
|
||||||
|
outFile, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outFile.Close()
|
||||||
|
|
||||||
|
return c.GenBashCompletion(outFile)
|
||||||
|
}
|
||||||
484
vendor/github.com/spf13/cobra/bash_completionsV2.go
generated
vendored
Normal file
484
vendor/github.com/spf13/cobra/bash_completionsV2.go
generated
vendored
Normal file
@@ -0,0 +1,484 @@
|
|||||||
|
// Copyright 2013-2023 The Cobra Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Command) genBashCompletion(w io.Writer, includeDesc bool) error {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
genBashComp(buf, c.Name(), includeDesc)
|
||||||
|
_, err := buf.WriteTo(w)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func genBashComp(buf io.StringWriter, name string, includeDesc bool) {
|
||||||
|
compCmd := ShellCompRequestCmd
|
||||||
|
if !includeDesc {
|
||||||
|
compCmd = ShellCompNoDescRequestCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(`# bash completion V2 for %-36[1]s -*- shell-script -*-
|
||||||
|
|
||||||
|
__%[1]s_debug()
|
||||||
|
{
|
||||||
|
if [[ -n ${BASH_COMP_DEBUG_FILE-} ]]; then
|
||||||
|
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Macs have bash3 for which the bash-completion package doesn't include
|
||||||
|
# _init_completion. This is a minimal version of that function.
|
||||||
|
__%[1]s_init_completion()
|
||||||
|
{
|
||||||
|
COMPREPLY=()
|
||||||
|
_get_comp_words_by_ref "$@" cur prev words cword
|
||||||
|
}
|
||||||
|
|
||||||
|
# This function calls the %[1]s program to obtain the completion
|
||||||
|
# results and the directive. It fills the 'out' and 'directive' vars.
|
||||||
|
__%[1]s_get_completion_results() {
|
||||||
|
local requestComp lastParam lastChar args
|
||||||
|
|
||||||
|
# Prepare the command to request completions for the program.
|
||||||
|
# Calling ${words[0]} instead of directly %[1]s allows handling aliases
|
||||||
|
args=("${words[@]:1}")
|
||||||
|
requestComp="${words[0]} %[2]s ${args[*]}"
|
||||||
|
|
||||||
|
lastParam=${words[$((${#words[@]}-1))]}
|
||||||
|
lastChar=${lastParam:$((${#lastParam}-1)):1}
|
||||||
|
__%[1]s_debug "lastParam ${lastParam}, lastChar ${lastChar}"
|
||||||
|
|
||||||
|
if [[ -z ${cur} && ${lastChar} != = ]]; then
|
||||||
|
# If the last parameter is complete (there is a space following it)
|
||||||
|
# We add an extra empty parameter so we can indicate this to the go method.
|
||||||
|
__%[1]s_debug "Adding extra empty parameter"
|
||||||
|
requestComp="${requestComp} ''"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# When completing a flag with an = (e.g., %[1]s -n=<TAB>)
|
||||||
|
# bash focuses on the part after the =, so we need to remove
|
||||||
|
# the flag part from $cur
|
||||||
|
if [[ ${cur} == -*=* ]]; then
|
||||||
|
cur="${cur#*=}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
__%[1]s_debug "Calling ${requestComp}"
|
||||||
|
# Use eval to handle any environment variables and such
|
||||||
|
out=$(eval "${requestComp}" 2>/dev/null)
|
||||||
|
|
||||||
|
# Extract the directive integer at the very end of the output following a colon (:)
|
||||||
|
directive=${out##*:}
|
||||||
|
# Remove the directive
|
||||||
|
out=${out%%:*}
|
||||||
|
if [[ ${directive} == "${out}" ]]; then
|
||||||
|
# There is not directive specified
|
||||||
|
directive=0
|
||||||
|
fi
|
||||||
|
__%[1]s_debug "The completion directive is: ${directive}"
|
||||||
|
__%[1]s_debug "The completions are: ${out}"
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_process_completion_results() {
|
||||||
|
local shellCompDirectiveError=%[3]d
|
||||||
|
local shellCompDirectiveNoSpace=%[4]d
|
||||||
|
local shellCompDirectiveNoFileComp=%[5]d
|
||||||
|
local shellCompDirectiveFilterFileExt=%[6]d
|
||||||
|
local shellCompDirectiveFilterDirs=%[7]d
|
||||||
|
local shellCompDirectiveKeepOrder=%[8]d
|
||||||
|
|
||||||
|
if (((directive & shellCompDirectiveError) != 0)); then
|
||||||
|
# Error code. No completion.
|
||||||
|
__%[1]s_debug "Received error from custom completion go code"
|
||||||
|
return
|
||||||
|
else
|
||||||
|
if (((directive & shellCompDirectiveNoSpace) != 0)); then
|
||||||
|
if [[ $(type -t compopt) == builtin ]]; then
|
||||||
|
__%[1]s_debug "Activating no space"
|
||||||
|
compopt -o nospace
|
||||||
|
else
|
||||||
|
__%[1]s_debug "No space directive not supported in this version of bash"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if (((directive & shellCompDirectiveKeepOrder) != 0)); then
|
||||||
|
if [[ $(type -t compopt) == builtin ]]; then
|
||||||
|
# no sort isn't supported for bash less than < 4.4
|
||||||
|
if [[ ${BASH_VERSINFO[0]} -lt 4 || ( ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -lt 4 ) ]]; then
|
||||||
|
__%[1]s_debug "No sort directive not supported in this version of bash"
|
||||||
|
else
|
||||||
|
__%[1]s_debug "Activating keep order"
|
||||||
|
compopt -o nosort
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
__%[1]s_debug "No sort directive not supported in this version of bash"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if (((directive & shellCompDirectiveNoFileComp) != 0)); then
|
||||||
|
if [[ $(type -t compopt) == builtin ]]; then
|
||||||
|
__%[1]s_debug "Activating no file completion"
|
||||||
|
compopt +o default
|
||||||
|
else
|
||||||
|
__%[1]s_debug "No file completion directive not supported in this version of bash"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Separate activeHelp from normal completions
|
||||||
|
local completions=()
|
||||||
|
local activeHelp=()
|
||||||
|
__%[1]s_extract_activeHelp
|
||||||
|
|
||||||
|
if (((directive & shellCompDirectiveFilterFileExt) != 0)); then
|
||||||
|
# File extension filtering
|
||||||
|
local fullFilter="" filter filteringCmd
|
||||||
|
|
||||||
|
# Do not use quotes around the $completions variable or else newline
|
||||||
|
# characters will be kept.
|
||||||
|
for filter in ${completions[*]}; do
|
||||||
|
fullFilter+="$filter|"
|
||||||
|
done
|
||||||
|
|
||||||
|
filteringCmd="_filedir $fullFilter"
|
||||||
|
__%[1]s_debug "File filtering command: $filteringCmd"
|
||||||
|
$filteringCmd
|
||||||
|
elif (((directive & shellCompDirectiveFilterDirs) != 0)); then
|
||||||
|
# File completion for directories only
|
||||||
|
|
||||||
|
local subdir
|
||||||
|
subdir=${completions[0]}
|
||||||
|
if [[ -n $subdir ]]; then
|
||||||
|
__%[1]s_debug "Listing directories in $subdir"
|
||||||
|
pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return
|
||||||
|
else
|
||||||
|
__%[1]s_debug "Listing directories in ."
|
||||||
|
_filedir -d
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
__%[1]s_handle_completion_types
|
||||||
|
fi
|
||||||
|
|
||||||
|
__%[1]s_handle_special_char "$cur" :
|
||||||
|
__%[1]s_handle_special_char "$cur" =
|
||||||
|
|
||||||
|
# Print the activeHelp statements before we finish
|
||||||
|
__%[1]s_handle_activeHelp
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_handle_activeHelp() {
|
||||||
|
# Print the activeHelp statements
|
||||||
|
if ((${#activeHelp[*]} != 0)); then
|
||||||
|
if [ -z $COMP_TYPE ]; then
|
||||||
|
# Bash v3 does not set the COMP_TYPE variable.
|
||||||
|
printf "\n";
|
||||||
|
printf "%%s\n" "${activeHelp[@]}"
|
||||||
|
printf "\n"
|
||||||
|
__%[1]s_reprint_commandLine
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only print ActiveHelp on the second TAB press
|
||||||
|
if [ $COMP_TYPE -eq 63 ]; then
|
||||||
|
printf "\n"
|
||||||
|
printf "%%s\n" "${activeHelp[@]}"
|
||||||
|
|
||||||
|
if ((${#COMPREPLY[*]} == 0)); then
|
||||||
|
# When there are no completion choices from the program, file completion
|
||||||
|
# may kick in if the program has not disabled it; in such a case, we want
|
||||||
|
# to know if any files will match what the user typed, so that we know if
|
||||||
|
# there will be completions presented, so that we know how to handle ActiveHelp.
|
||||||
|
# To find out, we actually trigger the file completion ourselves;
|
||||||
|
# the call to _filedir will fill COMPREPLY if files match.
|
||||||
|
if (((directive & shellCompDirectiveNoFileComp) == 0)); then
|
||||||
|
__%[1]s_debug "Listing files"
|
||||||
|
_filedir
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ((${#COMPREPLY[*]} != 0)); then
|
||||||
|
# If there are completion choices to be shown, print a delimiter.
|
||||||
|
# Re-printing the command-line will automatically be done
|
||||||
|
# by the shell when it prints the completion choices.
|
||||||
|
printf -- "--"
|
||||||
|
else
|
||||||
|
# When there are no completion choices at all, we need
|
||||||
|
# to re-print the command-line since the shell will
|
||||||
|
# not be doing it itself.
|
||||||
|
__%[1]s_reprint_commandLine
|
||||||
|
fi
|
||||||
|
elif [ $COMP_TYPE -eq 37 ] || [ $COMP_TYPE -eq 42 ]; then
|
||||||
|
# For completion type: menu-complete/menu-complete-backward and insert-completions
|
||||||
|
# the completions are immediately inserted into the command-line, so we first
|
||||||
|
# print the activeHelp message and reprint the command-line since the shell won't.
|
||||||
|
printf "\n"
|
||||||
|
printf "%%s\n" "${activeHelp[@]}"
|
||||||
|
|
||||||
|
__%[1]s_reprint_commandLine
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_reprint_commandLine() {
|
||||||
|
# The prompt format is only available from bash 4.4.
|
||||||
|
# We test if it is available before using it.
|
||||||
|
if (x=${PS1@P}) 2> /dev/null; then
|
||||||
|
printf "%%s" "${PS1@P}${COMP_LINE[@]}"
|
||||||
|
else
|
||||||
|
# Can't print the prompt. Just print the
|
||||||
|
# text the user had typed, it is workable enough.
|
||||||
|
printf "%%s" "${COMP_LINE[@]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Separate activeHelp lines from real completions.
|
||||||
|
# Fills the $activeHelp and $completions arrays.
|
||||||
|
__%[1]s_extract_activeHelp() {
|
||||||
|
local activeHelpMarker="%[9]s"
|
||||||
|
local endIndex=${#activeHelpMarker}
|
||||||
|
|
||||||
|
while IFS='' read -r comp; do
|
||||||
|
[[ -z $comp ]] && continue
|
||||||
|
|
||||||
|
if [[ ${comp:0:endIndex} == $activeHelpMarker ]]; then
|
||||||
|
comp=${comp:endIndex}
|
||||||
|
__%[1]s_debug "ActiveHelp found: $comp"
|
||||||
|
if [[ -n $comp ]]; then
|
||||||
|
activeHelp+=("$comp")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Not an activeHelp line but a normal completion
|
||||||
|
completions+=("$comp")
|
||||||
|
fi
|
||||||
|
done <<<"${out}"
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_handle_completion_types() {
|
||||||
|
__%[1]s_debug "__%[1]s_handle_completion_types: COMP_TYPE is $COMP_TYPE"
|
||||||
|
|
||||||
|
case $COMP_TYPE in
|
||||||
|
37|42)
|
||||||
|
# Type: menu-complete/menu-complete-backward and insert-completions
|
||||||
|
# If the user requested inserting one completion at a time, or all
|
||||||
|
# completions at once on the command-line we must remove the descriptions.
|
||||||
|
# https://github.com/spf13/cobra/issues/1508
|
||||||
|
|
||||||
|
# If there are no completions, we don't need to do anything
|
||||||
|
(( ${#completions[@]} == 0 )) && return 0
|
||||||
|
|
||||||
|
local tab=$'\t'
|
||||||
|
|
||||||
|
# Strip any description and escape the completion to handled special characters
|
||||||
|
IFS=$'\n' read -ra completions -d '' < <(printf "%%q\n" "${completions[@]%%%%$tab*}")
|
||||||
|
|
||||||
|
# Only consider the completions that match
|
||||||
|
IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n'; compgen -W "${completions[*]}" -- "${cur}")
|
||||||
|
|
||||||
|
# compgen looses the escaping so we need to escape all completions again since they will
|
||||||
|
# all be inserted on the command-line.
|
||||||
|
IFS=$'\n' read -ra COMPREPLY -d '' < <(printf "%%q\n" "${COMPREPLY[@]}")
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
# Type: complete (normal completion)
|
||||||
|
__%[1]s_handle_standard_completion_case
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_handle_standard_completion_case() {
|
||||||
|
local tab=$'\t'
|
||||||
|
|
||||||
|
# If there are no completions, we don't need to do anything
|
||||||
|
(( ${#completions[@]} == 0 )) && return 0
|
||||||
|
|
||||||
|
# Short circuit to optimize if we don't have descriptions
|
||||||
|
if [[ "${completions[*]}" != *$tab* ]]; then
|
||||||
|
# First, escape the completions to handle special characters
|
||||||
|
IFS=$'\n' read -ra completions -d '' < <(printf "%%q\n" "${completions[@]}")
|
||||||
|
# Only consider the completions that match what the user typed
|
||||||
|
IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n'; compgen -W "${completions[*]}" -- "${cur}")
|
||||||
|
|
||||||
|
# compgen looses the escaping so, if there is only a single completion, we need to
|
||||||
|
# escape it again because it will be inserted on the command-line. If there are multiple
|
||||||
|
# completions, we don't want to escape them because they will be printed in a list
|
||||||
|
# and we don't want to show escape characters in that list.
|
||||||
|
if (( ${#COMPREPLY[@]} == 1 )); then
|
||||||
|
COMPREPLY[0]=$(printf "%%q" "${COMPREPLY[0]}")
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local longest=0
|
||||||
|
local compline
|
||||||
|
# Look for the longest completion so that we can format things nicely
|
||||||
|
while IFS='' read -r compline; do
|
||||||
|
[[ -z $compline ]] && continue
|
||||||
|
|
||||||
|
# Before checking if the completion matches what the user typed,
|
||||||
|
# we need to strip any description and escape the completion to handle special
|
||||||
|
# characters because those escape characters are part of what the user typed.
|
||||||
|
# Don't call "printf" in a sub-shell because it will be much slower
|
||||||
|
# since we are in a loop.
|
||||||
|
printf -v comp "%%q" "${compline%%%%$tab*}" &>/dev/null || comp=$(printf "%%q" "${compline%%%%$tab*}")
|
||||||
|
|
||||||
|
# Only consider the completions that match
|
||||||
|
[[ $comp == "$cur"* ]] || continue
|
||||||
|
|
||||||
|
# The completions matches. Add it to the list of full completions including
|
||||||
|
# its description. We don't escape the completion because it may get printed
|
||||||
|
# in a list if there are more than one and we don't want show escape characters
|
||||||
|
# in that list.
|
||||||
|
COMPREPLY+=("$compline")
|
||||||
|
|
||||||
|
# Strip any description before checking the length, and again, don't escape
|
||||||
|
# the completion because this length is only used when printing the completions
|
||||||
|
# in a list and we don't want show escape characters in that list.
|
||||||
|
comp=${compline%%%%$tab*}
|
||||||
|
if ((${#comp}>longest)); then
|
||||||
|
longest=${#comp}
|
||||||
|
fi
|
||||||
|
done < <(printf "%%s\n" "${completions[@]}")
|
||||||
|
|
||||||
|
# If there is a single completion left, remove the description text and escape any special characters
|
||||||
|
if ((${#COMPREPLY[*]} == 1)); then
|
||||||
|
__%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}"
|
||||||
|
COMPREPLY[0]=$(printf "%%q" "${COMPREPLY[0]%%%%$tab*}")
|
||||||
|
__%[1]s_debug "Removed description from single completion, which is now: ${COMPREPLY[0]}"
|
||||||
|
else
|
||||||
|
# Format the descriptions
|
||||||
|
__%[1]s_format_comp_descriptions $longest
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_handle_special_char()
|
||||||
|
{
|
||||||
|
local comp="$1"
|
||||||
|
local char=$2
|
||||||
|
if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then
|
||||||
|
local word=${comp%%"${comp##*${char}}"}
|
||||||
|
local idx=${#COMPREPLY[*]}
|
||||||
|
while ((--idx >= 0)); do
|
||||||
|
COMPREPLY[idx]=${COMPREPLY[idx]#"$word"}
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_format_comp_descriptions()
|
||||||
|
{
|
||||||
|
local tab=$'\t'
|
||||||
|
local comp desc maxdesclength
|
||||||
|
local longest=$1
|
||||||
|
|
||||||
|
local i ci
|
||||||
|
for ci in ${!COMPREPLY[*]}; do
|
||||||
|
comp=${COMPREPLY[ci]}
|
||||||
|
# Properly format the description string which follows a tab character if there is one
|
||||||
|
if [[ "$comp" == *$tab* ]]; then
|
||||||
|
__%[1]s_debug "Original comp: $comp"
|
||||||
|
desc=${comp#*$tab}
|
||||||
|
comp=${comp%%%%$tab*}
|
||||||
|
|
||||||
|
# $COLUMNS stores the current shell width.
|
||||||
|
# Remove an extra 4 because we add 2 spaces and 2 parentheses.
|
||||||
|
maxdesclength=$(( COLUMNS - longest - 4 ))
|
||||||
|
|
||||||
|
# Make sure we can fit a description of at least 8 characters
|
||||||
|
# if we are to align the descriptions.
|
||||||
|
if ((maxdesclength > 8)); then
|
||||||
|
# Add the proper number of spaces to align the descriptions
|
||||||
|
for ((i = ${#comp} ; i < longest ; i++)); do
|
||||||
|
comp+=" "
|
||||||
|
done
|
||||||
|
else
|
||||||
|
# Don't pad the descriptions so we can fit more text after the completion
|
||||||
|
maxdesclength=$(( COLUMNS - ${#comp} - 4 ))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If there is enough space for any description text,
|
||||||
|
# truncate the descriptions that are too long for the shell width
|
||||||
|
if ((maxdesclength > 0)); then
|
||||||
|
if ((${#desc} > maxdesclength)); then
|
||||||
|
desc=${desc:0:$(( maxdesclength - 1 ))}
|
||||||
|
desc+="…"
|
||||||
|
fi
|
||||||
|
comp+=" ($desc)"
|
||||||
|
fi
|
||||||
|
COMPREPLY[ci]=$comp
|
||||||
|
__%[1]s_debug "Final comp: $comp"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
__start_%[1]s()
|
||||||
|
{
|
||||||
|
local cur prev words cword split
|
||||||
|
|
||||||
|
COMPREPLY=()
|
||||||
|
|
||||||
|
# Call _init_completion from the bash-completion package
|
||||||
|
# to prepare the arguments properly
|
||||||
|
if declare -F _init_completion >/dev/null 2>&1; then
|
||||||
|
_init_completion -n =: || return
|
||||||
|
else
|
||||||
|
__%[1]s_init_completion -n =: || return
|
||||||
|
fi
|
||||||
|
|
||||||
|
__%[1]s_debug
|
||||||
|
__%[1]s_debug "========= starting completion logic =========="
|
||||||
|
__%[1]s_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword"
|
||||||
|
|
||||||
|
# The user could have moved the cursor backwards on the command-line.
|
||||||
|
# We need to trigger completion from the $cword location, so we need
|
||||||
|
# to truncate the command-line ($words) up to the $cword location.
|
||||||
|
words=("${words[@]:0:$cword+1}")
|
||||||
|
__%[1]s_debug "Truncated words[*]: ${words[*]},"
|
||||||
|
|
||||||
|
local out directive
|
||||||
|
__%[1]s_get_completion_results
|
||||||
|
__%[1]s_process_completion_results
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $(type -t compopt) = "builtin" ]]; then
|
||||||
|
complete -o default -F __start_%[1]s %[1]s
|
||||||
|
else
|
||||||
|
complete -o default -o nospace -F __start_%[1]s %[1]s
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ex: ts=4 sw=4 et filetype=sh
|
||||||
|
`, name, compCmd,
|
||||||
|
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
||||||
|
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder,
|
||||||
|
activeHelpMarker))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenBashCompletionFileV2 generates Bash completion version 2.
|
||||||
|
func (c *Command) GenBashCompletionFileV2(filename string, includeDesc bool) error {
|
||||||
|
outFile, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outFile.Close()
|
||||||
|
|
||||||
|
return c.GenBashCompletionV2(outFile, includeDesc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenBashCompletionV2 generates Bash completion file version 2
|
||||||
|
// and writes it to the passed writer.
|
||||||
|
func (c *Command) GenBashCompletionV2(w io.Writer, includeDesc bool) error {
|
||||||
|
return c.genBashCompletion(w, includeDesc)
|
||||||
|
}
|
||||||
246
vendor/github.com/spf13/cobra/cobra.go
generated
vendored
Normal file
246
vendor/github.com/spf13/cobra/cobra.go
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
// Copyright 2013-2023 The Cobra Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Commands similar to git, go tools and other modern CLI tools
|
||||||
|
// inspired by go, go-Commander, gh and subcommand
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
var templateFuncs = template.FuncMap{
|
||||||
|
"trim": strings.TrimSpace,
|
||||||
|
"trimRightSpace": trimRightSpace,
|
||||||
|
"trimTrailingWhitespaces": trimRightSpace,
|
||||||
|
"appendIfNotPresent": appendIfNotPresent,
|
||||||
|
"rpad": rpad,
|
||||||
|
"gt": Gt,
|
||||||
|
"eq": Eq,
|
||||||
|
}
|
||||||
|
|
||||||
|
var initializers []func()
|
||||||
|
var finalizers []func()
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultPrefixMatching = false
|
||||||
|
defaultCommandSorting = true
|
||||||
|
defaultCaseInsensitive = false
|
||||||
|
defaultTraverseRunHooks = false
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnablePrefixMatching allows setting automatic prefix matching. Automatic prefix matching can be a dangerous thing
|
||||||
|
// to automatically enable in CLI tools.
|
||||||
|
// Set this to true to enable it.
|
||||||
|
var EnablePrefixMatching = defaultPrefixMatching
|
||||||
|
|
||||||
|
// EnableCommandSorting controls sorting of the slice of commands, which is turned on by default.
|
||||||
|
// To disable sorting, set it to false.
|
||||||
|
var EnableCommandSorting = defaultCommandSorting
|
||||||
|
|
||||||
|
// EnableCaseInsensitive allows case-insensitive commands names. (case sensitive by default)
|
||||||
|
var EnableCaseInsensitive = defaultCaseInsensitive
|
||||||
|
|
||||||
|
// EnableTraverseRunHooks executes persistent pre-run and post-run hooks from all parents.
|
||||||
|
// By default this is disabled, which means only the first run hook to be found is executed.
|
||||||
|
var EnableTraverseRunHooks = defaultTraverseRunHooks
|
||||||
|
|
||||||
|
// MousetrapHelpText enables an information splash screen on Windows
|
||||||
|
// if the CLI is started from explorer.exe.
|
||||||
|
// To disable the mousetrap, just set this variable to blank string ("").
|
||||||
|
// Works only on Microsoft Windows.
|
||||||
|
var MousetrapHelpText = `This is a command line tool.
|
||||||
|
|
||||||
|
You need to open cmd.exe and run it from there.
|
||||||
|
`
|
||||||
|
|
||||||
|
// MousetrapDisplayDuration controls how long the MousetrapHelpText message is displayed on Windows
|
||||||
|
// if the CLI is started from explorer.exe. Set to 0 to wait for the return key to be pressed.
|
||||||
|
// To disable the mousetrap, just set MousetrapHelpText to blank string ("").
|
||||||
|
// Works only on Microsoft Windows.
|
||||||
|
var MousetrapDisplayDuration = 5 * time.Second
|
||||||
|
|
||||||
|
// AddTemplateFunc adds a template function that's available to Usage and Help
|
||||||
|
// template generation.
|
||||||
|
func AddTemplateFunc(name string, tmplFunc interface{}) {
|
||||||
|
templateFuncs[name] = tmplFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTemplateFuncs adds multiple template functions that are available to Usage and
|
||||||
|
// Help template generation.
|
||||||
|
func AddTemplateFuncs(tmplFuncs template.FuncMap) {
|
||||||
|
for k, v := range tmplFuncs {
|
||||||
|
templateFuncs[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnInitialize sets the passed functions to be run when each command's
|
||||||
|
// Execute method is called.
|
||||||
|
func OnInitialize(y ...func()) {
|
||||||
|
initializers = append(initializers, y...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnFinalize sets the passed functions to be run when each command's
|
||||||
|
// Execute method is terminated.
|
||||||
|
func OnFinalize(y ...func()) {
|
||||||
|
finalizers = append(finalizers, y...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME Gt is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra.
|
||||||
|
|
||||||
|
// Gt takes two types and checks whether the first type is greater than the second. In case of types Arrays, Chans,
|
||||||
|
// Maps and Slices, Gt will compare their lengths. Ints are compared directly while strings are first parsed as
|
||||||
|
// ints and then compared.
|
||||||
|
func Gt(a interface{}, b interface{}) bool {
|
||||||
|
var left, right int64
|
||||||
|
av := reflect.ValueOf(a)
|
||||||
|
|
||||||
|
switch av.Kind() {
|
||||||
|
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
|
||||||
|
left = int64(av.Len())
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
left = av.Int()
|
||||||
|
case reflect.String:
|
||||||
|
left, _ = strconv.ParseInt(av.String(), 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
bv := reflect.ValueOf(b)
|
||||||
|
|
||||||
|
switch bv.Kind() {
|
||||||
|
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
|
||||||
|
right = int64(bv.Len())
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
right = bv.Int()
|
||||||
|
case reflect.String:
|
||||||
|
right, _ = strconv.ParseInt(bv.String(), 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
return left > right
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME Eq is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra.
|
||||||
|
|
||||||
|
// Eq takes two types and checks whether they are equal. Supported types are int and string. Unsupported types will panic.
|
||||||
|
func Eq(a interface{}, b interface{}) bool {
|
||||||
|
av := reflect.ValueOf(a)
|
||||||
|
bv := reflect.ValueOf(b)
|
||||||
|
|
||||||
|
switch av.Kind() {
|
||||||
|
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
|
||||||
|
panic("Eq called on unsupported type")
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return av.Int() == bv.Int()
|
||||||
|
case reflect.String:
|
||||||
|
return av.String() == bv.String()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimRightSpace(s string) string {
|
||||||
|
return strings.TrimRightFunc(s, unicode.IsSpace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME appendIfNotPresent is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra.
|
||||||
|
|
||||||
|
// appendIfNotPresent will append stringToAppend to the end of s, but only if it's not yet present in s.
|
||||||
|
func appendIfNotPresent(s, stringToAppend string) string {
|
||||||
|
if strings.Contains(s, stringToAppend) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return s + " " + stringToAppend
|
||||||
|
}
|
||||||
|
|
||||||
|
// rpad adds padding to the right of a string.
|
||||||
|
func rpad(s string, padding int) string {
|
||||||
|
formattedString := fmt.Sprintf("%%-%ds", padding)
|
||||||
|
return fmt.Sprintf(formattedString, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tmpl(text string) *tmplFunc {
|
||||||
|
return &tmplFunc{
|
||||||
|
tmpl: text,
|
||||||
|
fn: func(w io.Writer, data interface{}) error {
|
||||||
|
t := template.New("top")
|
||||||
|
t.Funcs(templateFuncs)
|
||||||
|
template.Must(t.Parse(text))
|
||||||
|
return t.Execute(w, data)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ld compares two strings and returns the levenshtein distance between them.
|
||||||
|
func ld(s, t string, ignoreCase bool) int {
|
||||||
|
if ignoreCase {
|
||||||
|
s = strings.ToLower(s)
|
||||||
|
t = strings.ToLower(t)
|
||||||
|
}
|
||||||
|
d := make([][]int, len(s)+1)
|
||||||
|
for i := range d {
|
||||||
|
d[i] = make([]int, len(t)+1)
|
||||||
|
d[i][0] = i
|
||||||
|
}
|
||||||
|
for j := range d[0] {
|
||||||
|
d[0][j] = j
|
||||||
|
}
|
||||||
|
for j := 1; j <= len(t); j++ {
|
||||||
|
for i := 1; i <= len(s); i++ {
|
||||||
|
if s[i-1] == t[j-1] {
|
||||||
|
d[i][j] = d[i-1][j-1]
|
||||||
|
} else {
|
||||||
|
min := d[i-1][j]
|
||||||
|
if d[i][j-1] < min {
|
||||||
|
min = d[i][j-1]
|
||||||
|
}
|
||||||
|
if d[i-1][j-1] < min {
|
||||||
|
min = d[i-1][j-1]
|
||||||
|
}
|
||||||
|
d[i][j] = min + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return d[len(s)][len(t)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringInSlice(a string, list []string) bool {
|
||||||
|
for _, b := range list {
|
||||||
|
if b == a {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckErr prints the msg with the prefix 'Error:' and exits with error code 1. If the msg is nil, it does nothing.
|
||||||
|
func CheckErr(msg interface{}) {
|
||||||
|
if msg != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error:", msg)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteStringAndCheck writes a string into a buffer, and checks if the error is not nil.
|
||||||
|
func WriteStringAndCheck(b io.StringWriter, s string) {
|
||||||
|
_, err := b.WriteString(s)
|
||||||
|
CheckErr(err)
|
||||||
|
}
|
||||||
2072
vendor/github.com/spf13/cobra/command.go
generated
vendored
Normal file
2072
vendor/github.com/spf13/cobra/command.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
20
vendor/github.com/spf13/cobra/command_notwin.go
generated
vendored
Normal file
20
vendor/github.com/spf13/cobra/command_notwin.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2013-2023 The Cobra Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
var preExecHookFn func(*Command)
|
||||||
41
vendor/github.com/spf13/cobra/command_win.go
generated
vendored
Normal file
41
vendor/github.com/spf13/cobra/command_win.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2013-2023 The Cobra Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/inconshreveable/mousetrap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var preExecHookFn = preExecHook
|
||||||
|
|
||||||
|
func preExecHook(c *Command) {
|
||||||
|
if MousetrapHelpText != "" && mousetrap.StartedByExplorer() {
|
||||||
|
c.Print(MousetrapHelpText)
|
||||||
|
if MousetrapDisplayDuration > 0 {
|
||||||
|
time.Sleep(MousetrapDisplayDuration)
|
||||||
|
} else {
|
||||||
|
c.Println("Press return to continue...")
|
||||||
|
fmt.Scanln()
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
1020
vendor/github.com/spf13/cobra/completions.go
generated
vendored
Normal file
1020
vendor/github.com/spf13/cobra/completions.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
292
vendor/github.com/spf13/cobra/fish_completions.go
generated
vendored
Normal file
292
vendor/github.com/spf13/cobra/fish_completions.go
generated
vendored
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
// Copyright 2013-2023 The Cobra Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func genFishComp(buf io.StringWriter, name string, includeDesc bool) {
|
||||||
|
// Variables should not contain a '-' or ':' character
|
||||||
|
nameForVar := name
|
||||||
|
nameForVar = strings.ReplaceAll(nameForVar, "-", "_")
|
||||||
|
nameForVar = strings.ReplaceAll(nameForVar, ":", "_")
|
||||||
|
|
||||||
|
compCmd := ShellCompRequestCmd
|
||||||
|
if !includeDesc {
|
||||||
|
compCmd = ShellCompNoDescRequestCmd
|
||||||
|
}
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf("# fish completion for %-36s -*- shell-script -*-\n", name))
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(`
|
||||||
|
function __%[1]s_debug
|
||||||
|
set -l file "$BASH_COMP_DEBUG_FILE"
|
||||||
|
if test -n "$file"
|
||||||
|
echo "$argv" >> $file
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function __%[1]s_perform_completion
|
||||||
|
__%[1]s_debug "Starting __%[1]s_perform_completion"
|
||||||
|
|
||||||
|
# Extract all args except the last one
|
||||||
|
set -l args (commandline -opc)
|
||||||
|
# Extract the last arg and escape it in case it is a space
|
||||||
|
set -l lastArg (string escape -- (commandline -ct))
|
||||||
|
|
||||||
|
__%[1]s_debug "args: $args"
|
||||||
|
__%[1]s_debug "last arg: $lastArg"
|
||||||
|
|
||||||
|
# Disable ActiveHelp which is not supported for fish shell
|
||||||
|
set -l requestComp "%[10]s=0 $args[1] %[3]s $args[2..-1] $lastArg"
|
||||||
|
|
||||||
|
__%[1]s_debug "Calling $requestComp"
|
||||||
|
set -l results (eval $requestComp 2> /dev/null)
|
||||||
|
|
||||||
|
# Some programs may output extra empty lines after the directive.
|
||||||
|
# Let's ignore them or else it will break completion.
|
||||||
|
# Ref: https://github.com/spf13/cobra/issues/1279
|
||||||
|
for line in $results[-1..1]
|
||||||
|
if test (string trim -- $line) = ""
|
||||||
|
# Found an empty line, remove it
|
||||||
|
set results $results[1..-2]
|
||||||
|
else
|
||||||
|
# Found non-empty line, we have our proper output
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
set -l comps $results[1..-2]
|
||||||
|
set -l directiveLine $results[-1]
|
||||||
|
|
||||||
|
# For Fish, when completing a flag with an = (e.g., <program> -n=<TAB>)
|
||||||
|
# completions must be prefixed with the flag
|
||||||
|
set -l flagPrefix (string match -r -- '-.*=' "$lastArg")
|
||||||
|
|
||||||
|
__%[1]s_debug "Comps: $comps"
|
||||||
|
__%[1]s_debug "DirectiveLine: $directiveLine"
|
||||||
|
__%[1]s_debug "flagPrefix: $flagPrefix"
|
||||||
|
|
||||||
|
for comp in $comps
|
||||||
|
printf "%%s%%s\n" "$flagPrefix" "$comp"
|
||||||
|
end
|
||||||
|
|
||||||
|
printf "%%s\n" "$directiveLine"
|
||||||
|
end
|
||||||
|
|
||||||
|
# this function limits calls to __%[1]s_perform_completion, by caching the result behind $__%[1]s_perform_completion_once_result
|
||||||
|
function __%[1]s_perform_completion_once
|
||||||
|
__%[1]s_debug "Starting __%[1]s_perform_completion_once"
|
||||||
|
|
||||||
|
if test -n "$__%[1]s_perform_completion_once_result"
|
||||||
|
__%[1]s_debug "Seems like a valid result already exists, skipping __%[1]s_perform_completion"
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
set --global __%[1]s_perform_completion_once_result (__%[1]s_perform_completion)
|
||||||
|
if test -z "$__%[1]s_perform_completion_once_result"
|
||||||
|
__%[1]s_debug "No completions, probably due to a failure"
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
__%[1]s_debug "Performed completions and set __%[1]s_perform_completion_once_result"
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
# this function is used to clear the $__%[1]s_perform_completion_once_result variable after completions are run
|
||||||
|
function __%[1]s_clear_perform_completion_once_result
|
||||||
|
__%[1]s_debug ""
|
||||||
|
__%[1]s_debug "========= clearing previously set __%[1]s_perform_completion_once_result variable =========="
|
||||||
|
set --erase __%[1]s_perform_completion_once_result
|
||||||
|
__%[1]s_debug "Successfully erased the variable __%[1]s_perform_completion_once_result"
|
||||||
|
end
|
||||||
|
|
||||||
|
function __%[1]s_requires_order_preservation
|
||||||
|
__%[1]s_debug ""
|
||||||
|
__%[1]s_debug "========= checking if order preservation is required =========="
|
||||||
|
|
||||||
|
__%[1]s_perform_completion_once
|
||||||
|
if test -z "$__%[1]s_perform_completion_once_result"
|
||||||
|
__%[1]s_debug "Error determining if order preservation is required"
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
set -l directive (string sub --start 2 $__%[1]s_perform_completion_once_result[-1])
|
||||||
|
__%[1]s_debug "Directive is: $directive"
|
||||||
|
|
||||||
|
set -l shellCompDirectiveKeepOrder %[9]d
|
||||||
|
set -l keeporder (math (math --scale 0 $directive / $shellCompDirectiveKeepOrder) %% 2)
|
||||||
|
__%[1]s_debug "Keeporder is: $keeporder"
|
||||||
|
|
||||||
|
if test $keeporder -ne 0
|
||||||
|
__%[1]s_debug "This does require order preservation"
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
__%[1]s_debug "This doesn't require order preservation"
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# This function does two things:
|
||||||
|
# - Obtain the completions and store them in the global __%[1]s_comp_results
|
||||||
|
# - Return false if file completion should be performed
|
||||||
|
function __%[1]s_prepare_completions
|
||||||
|
__%[1]s_debug ""
|
||||||
|
__%[1]s_debug "========= starting completion logic =========="
|
||||||
|
|
||||||
|
# Start fresh
|
||||||
|
set --erase __%[1]s_comp_results
|
||||||
|
|
||||||
|
__%[1]s_perform_completion_once
|
||||||
|
__%[1]s_debug "Completion results: $__%[1]s_perform_completion_once_result"
|
||||||
|
|
||||||
|
if test -z "$__%[1]s_perform_completion_once_result"
|
||||||
|
__%[1]s_debug "No completion, probably due to a failure"
|
||||||
|
# Might as well do file completion, in case it helps
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
set -l directive (string sub --start 2 $__%[1]s_perform_completion_once_result[-1])
|
||||||
|
set --global __%[1]s_comp_results $__%[1]s_perform_completion_once_result[1..-2]
|
||||||
|
|
||||||
|
__%[1]s_debug "Completions are: $__%[1]s_comp_results"
|
||||||
|
__%[1]s_debug "Directive is: $directive"
|
||||||
|
|
||||||
|
set -l shellCompDirectiveError %[4]d
|
||||||
|
set -l shellCompDirectiveNoSpace %[5]d
|
||||||
|
set -l shellCompDirectiveNoFileComp %[6]d
|
||||||
|
set -l shellCompDirectiveFilterFileExt %[7]d
|
||||||
|
set -l shellCompDirectiveFilterDirs %[8]d
|
||||||
|
|
||||||
|
if test -z "$directive"
|
||||||
|
set directive 0
|
||||||
|
end
|
||||||
|
|
||||||
|
set -l compErr (math (math --scale 0 $directive / $shellCompDirectiveError) %% 2)
|
||||||
|
if test $compErr -eq 1
|
||||||
|
__%[1]s_debug "Received error directive: aborting."
|
||||||
|
# Might as well do file completion, in case it helps
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
set -l filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) %% 2)
|
||||||
|
set -l dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) %% 2)
|
||||||
|
if test $filefilter -eq 1; or test $dirfilter -eq 1
|
||||||
|
__%[1]s_debug "File extension filtering or directory filtering not supported"
|
||||||
|
# Do full file completion instead
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
set -l nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) %% 2)
|
||||||
|
set -l nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) %% 2)
|
||||||
|
|
||||||
|
__%[1]s_debug "nospace: $nospace, nofiles: $nofiles"
|
||||||
|
|
||||||
|
# If we want to prevent a space, or if file completion is NOT disabled,
|
||||||
|
# we need to count the number of valid completions.
|
||||||
|
# To do so, we will filter on prefix as the completions we have received
|
||||||
|
# may not already be filtered so as to allow fish to match on different
|
||||||
|
# criteria than the prefix.
|
||||||
|
if test $nospace -ne 0; or test $nofiles -eq 0
|
||||||
|
set -l prefix (commandline -t | string escape --style=regex)
|
||||||
|
__%[1]s_debug "prefix: $prefix"
|
||||||
|
|
||||||
|
set -l completions (string match -r -- "^$prefix.*" $__%[1]s_comp_results)
|
||||||
|
set --global __%[1]s_comp_results $completions
|
||||||
|
__%[1]s_debug "Filtered completions are: $__%[1]s_comp_results"
|
||||||
|
|
||||||
|
# Important not to quote the variable for count to work
|
||||||
|
set -l numComps (count $__%[1]s_comp_results)
|
||||||
|
__%[1]s_debug "numComps: $numComps"
|
||||||
|
|
||||||
|
if test $numComps -eq 1; and test $nospace -ne 0
|
||||||
|
# We must first split on \t to get rid of the descriptions to be
|
||||||
|
# able to check what the actual completion will be.
|
||||||
|
# We don't need descriptions anyway since there is only a single
|
||||||
|
# real completion which the shell will expand immediately.
|
||||||
|
set -l split (string split --max 1 \t $__%[1]s_comp_results[1])
|
||||||
|
|
||||||
|
# Fish won't add a space if the completion ends with any
|
||||||
|
# of the following characters: @=/:.,
|
||||||
|
set -l lastChar (string sub -s -1 -- $split)
|
||||||
|
if not string match -r -q "[@=/:.,]" -- "$lastChar"
|
||||||
|
# In other cases, to support the "nospace" directive we trick the shell
|
||||||
|
# by outputting an extra, longer completion.
|
||||||
|
__%[1]s_debug "Adding second completion to perform nospace directive"
|
||||||
|
set --global __%[1]s_comp_results $split[1] $split[1].
|
||||||
|
__%[1]s_debug "Completions are now: $__%[1]s_comp_results"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if test $numComps -eq 0; and test $nofiles -eq 0
|
||||||
|
# To be consistent with bash and zsh, we only trigger file
|
||||||
|
# completion when there are no other completions
|
||||||
|
__%[1]s_debug "Requesting file completion"
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves
|
||||||
|
# so we can properly delete any completions provided by another script.
|
||||||
|
# Only do this if the program can be found, or else fish may print some errors; besides,
|
||||||
|
# the existing completions will only be loaded if the program can be found.
|
||||||
|
if type -q "%[2]s"
|
||||||
|
# The space after the program name is essential to trigger completion for the program
|
||||||
|
# and not completion of the program name itself.
|
||||||
|
# Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
|
||||||
|
complete --do-complete "%[2]s " > /dev/null 2>&1
|
||||||
|
end
|
||||||
|
|
||||||
|
# Remove any pre-existing completions for the program since we will be handling all of them.
|
||||||
|
complete -c %[2]s -e
|
||||||
|
|
||||||
|
# this will get called after the two calls below and clear the $__%[1]s_perform_completion_once_result global
|
||||||
|
complete -c %[2]s -n '__%[1]s_clear_perform_completion_once_result'
|
||||||
|
# The call to __%[1]s_prepare_completions will setup __%[1]s_comp_results
|
||||||
|
# which provides the program's completion choices.
|
||||||
|
# If this doesn't require order preservation, we don't use the -k flag
|
||||||
|
complete -c %[2]s -n 'not __%[1]s_requires_order_preservation && __%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results'
|
||||||
|
# otherwise we use the -k flag
|
||||||
|
complete -k -c %[2]s -n '__%[1]s_requires_order_preservation && __%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results'
|
||||||
|
`, nameForVar, name, compCmd,
|
||||||
|
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
||||||
|
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpEnvVar(name)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenFishCompletion generates fish completion file and writes to the passed writer.
|
||||||
|
func (c *Command) GenFishCompletion(w io.Writer, includeDesc bool) error {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
genFishComp(buf, c.Name(), includeDesc)
|
||||||
|
_, err := buf.WriteTo(w)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenFishCompletionFile generates fish completion file.
|
||||||
|
func (c *Command) GenFishCompletionFile(filename string, includeDesc bool) error {
|
||||||
|
outFile, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outFile.Close()
|
||||||
|
|
||||||
|
return c.GenFishCompletion(outFile, includeDesc)
|
||||||
|
}
|
||||||
290
vendor/github.com/spf13/cobra/flag_groups.go
generated
vendored
Normal file
290
vendor/github.com/spf13/cobra/flag_groups.go
generated
vendored
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
// Copyright 2013-2023 The Cobra Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
flag "github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
requiredAsGroupAnnotation = "cobra_annotation_required_if_others_set"
|
||||||
|
oneRequiredAnnotation = "cobra_annotation_one_required"
|
||||||
|
mutuallyExclusiveAnnotation = "cobra_annotation_mutually_exclusive"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MarkFlagsRequiredTogether marks the given flags with annotations so that Cobra errors
|
||||||
|
// if the command is invoked with a subset (but not all) of the given flags.
|
||||||
|
func (c *Command) MarkFlagsRequiredTogether(flagNames ...string) {
|
||||||
|
c.mergePersistentFlags()
|
||||||
|
for _, v := range flagNames {
|
||||||
|
f := c.Flags().Lookup(v)
|
||||||
|
if f == nil {
|
||||||
|
panic(fmt.Sprintf("Failed to find flag %q and mark it as being required in a flag group", v))
|
||||||
|
}
|
||||||
|
if err := c.Flags().SetAnnotation(v, requiredAsGroupAnnotation, append(f.Annotations[requiredAsGroupAnnotation], strings.Join(flagNames, " "))); err != nil {
|
||||||
|
// Only errs if the flag isn't found.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkFlagsOneRequired marks the given flags with annotations so that Cobra errors
|
||||||
|
// if the command is invoked without at least one flag from the given set of flags.
|
||||||
|
func (c *Command) MarkFlagsOneRequired(flagNames ...string) {
|
||||||
|
c.mergePersistentFlags()
|
||||||
|
for _, v := range flagNames {
|
||||||
|
f := c.Flags().Lookup(v)
|
||||||
|
if f == nil {
|
||||||
|
panic(fmt.Sprintf("Failed to find flag %q and mark it as being in a one-required flag group", v))
|
||||||
|
}
|
||||||
|
if err := c.Flags().SetAnnotation(v, oneRequiredAnnotation, append(f.Annotations[oneRequiredAnnotation], strings.Join(flagNames, " "))); err != nil {
|
||||||
|
// Only errs if the flag isn't found.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkFlagsMutuallyExclusive marks the given flags with annotations so that Cobra errors
|
||||||
|
// if the command is invoked with more than one flag from the given set of flags.
|
||||||
|
func (c *Command) MarkFlagsMutuallyExclusive(flagNames ...string) {
|
||||||
|
c.mergePersistentFlags()
|
||||||
|
for _, v := range flagNames {
|
||||||
|
f := c.Flags().Lookup(v)
|
||||||
|
if f == nil {
|
||||||
|
panic(fmt.Sprintf("Failed to find flag %q and mark it as being in a mutually exclusive flag group", v))
|
||||||
|
}
|
||||||
|
// Each time this is called is a single new entry; this allows it to be a member of multiple groups if needed.
|
||||||
|
if err := c.Flags().SetAnnotation(v, mutuallyExclusiveAnnotation, append(f.Annotations[mutuallyExclusiveAnnotation], strings.Join(flagNames, " "))); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateFlagGroups validates the mutuallyExclusive/oneRequired/requiredAsGroup logic and returns the
|
||||||
|
// first error encountered.
|
||||||
|
func (c *Command) ValidateFlagGroups() error {
|
||||||
|
if c.DisableFlagParsing {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := c.Flags()
|
||||||
|
|
||||||
|
// groupStatus format is the list of flags as a unique ID,
|
||||||
|
// then a map of each flag name and whether it is set or not.
|
||||||
|
groupStatus := map[string]map[string]bool{}
|
||||||
|
oneRequiredGroupStatus := map[string]map[string]bool{}
|
||||||
|
mutuallyExclusiveGroupStatus := map[string]map[string]bool{}
|
||||||
|
flags.VisitAll(func(pflag *flag.Flag) {
|
||||||
|
processFlagForGroupAnnotation(flags, pflag, requiredAsGroupAnnotation, groupStatus)
|
||||||
|
processFlagForGroupAnnotation(flags, pflag, oneRequiredAnnotation, oneRequiredGroupStatus)
|
||||||
|
processFlagForGroupAnnotation(flags, pflag, mutuallyExclusiveAnnotation, mutuallyExclusiveGroupStatus)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := validateRequiredFlagGroups(groupStatus); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := validateOneRequiredFlagGroups(oneRequiredGroupStatus); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := validateExclusiveFlagGroups(mutuallyExclusiveGroupStatus); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasAllFlags(fs *flag.FlagSet, flagnames ...string) bool {
|
||||||
|
for _, fname := range flagnames {
|
||||||
|
f := fs.Lookup(fname)
|
||||||
|
if f == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func processFlagForGroupAnnotation(flags *flag.FlagSet, pflag *flag.Flag, annotation string, groupStatus map[string]map[string]bool) {
|
||||||
|
groupInfo, found := pflag.Annotations[annotation]
|
||||||
|
if found {
|
||||||
|
for _, group := range groupInfo {
|
||||||
|
if groupStatus[group] == nil {
|
||||||
|
flagnames := strings.Split(group, " ")
|
||||||
|
|
||||||
|
// Only consider this flag group at all if all the flags are defined.
|
||||||
|
if !hasAllFlags(flags, flagnames...) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
groupStatus[group] = make(map[string]bool, len(flagnames))
|
||||||
|
for _, name := range flagnames {
|
||||||
|
groupStatus[group][name] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
groupStatus[group][pflag.Name] = pflag.Changed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateRequiredFlagGroups(data map[string]map[string]bool) error {
|
||||||
|
keys := sortedKeys(data)
|
||||||
|
for _, flagList := range keys {
|
||||||
|
flagnameAndStatus := data[flagList]
|
||||||
|
|
||||||
|
unset := []string{}
|
||||||
|
for flagname, isSet := range flagnameAndStatus {
|
||||||
|
if !isSet {
|
||||||
|
unset = append(unset, flagname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(unset) == len(flagnameAndStatus) || len(unset) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort values, so they can be tested/scripted against consistently.
|
||||||
|
sort.Strings(unset)
|
||||||
|
return fmt.Errorf("if any flags in the group [%v] are set they must all be set; missing %v", flagList, unset)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateOneRequiredFlagGroups(data map[string]map[string]bool) error {
|
||||||
|
keys := sortedKeys(data)
|
||||||
|
for _, flagList := range keys {
|
||||||
|
flagnameAndStatus := data[flagList]
|
||||||
|
var set []string
|
||||||
|
for flagname, isSet := range flagnameAndStatus {
|
||||||
|
if isSet {
|
||||||
|
set = append(set, flagname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(set) >= 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort values, so they can be tested/scripted against consistently.
|
||||||
|
sort.Strings(set)
|
||||||
|
return fmt.Errorf("at least one of the flags in the group [%v] is required", flagList)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateExclusiveFlagGroups(data map[string]map[string]bool) error {
|
||||||
|
keys := sortedKeys(data)
|
||||||
|
for _, flagList := range keys {
|
||||||
|
flagnameAndStatus := data[flagList]
|
||||||
|
var set []string
|
||||||
|
for flagname, isSet := range flagnameAndStatus {
|
||||||
|
if isSet {
|
||||||
|
set = append(set, flagname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(set) == 0 || len(set) == 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort values, so they can be tested/scripted against consistently.
|
||||||
|
sort.Strings(set)
|
||||||
|
return fmt.Errorf("if any flags in the group [%v] are set none of the others can be; %v were all set", flagList, set)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortedKeys(m map[string]map[string]bool) []string {
|
||||||
|
keys := make([]string, len(m))
|
||||||
|
i := 0
|
||||||
|
for k := range m {
|
||||||
|
keys[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// enforceFlagGroupsForCompletion will do the following:
|
||||||
|
// - when a flag in a group is present, other flags in the group will be marked required
|
||||||
|
// - when none of the flags in a one-required group are present, all flags in the group will be marked required
|
||||||
|
// - when a flag in a mutually exclusive group is present, other flags in the group will be marked as hidden
|
||||||
|
// This allows the standard completion logic to behave appropriately for flag groups
|
||||||
|
func (c *Command) enforceFlagGroupsForCompletion() {
|
||||||
|
if c.DisableFlagParsing {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := c.Flags()
|
||||||
|
groupStatus := map[string]map[string]bool{}
|
||||||
|
oneRequiredGroupStatus := map[string]map[string]bool{}
|
||||||
|
mutuallyExclusiveGroupStatus := map[string]map[string]bool{}
|
||||||
|
c.Flags().VisitAll(func(pflag *flag.Flag) {
|
||||||
|
processFlagForGroupAnnotation(flags, pflag, requiredAsGroupAnnotation, groupStatus)
|
||||||
|
processFlagForGroupAnnotation(flags, pflag, oneRequiredAnnotation, oneRequiredGroupStatus)
|
||||||
|
processFlagForGroupAnnotation(flags, pflag, mutuallyExclusiveAnnotation, mutuallyExclusiveGroupStatus)
|
||||||
|
})
|
||||||
|
|
||||||
|
// If a flag that is part of a group is present, we make all the other flags
|
||||||
|
// of that group required so that the shell completion suggests them automatically
|
||||||
|
for flagList, flagnameAndStatus := range groupStatus {
|
||||||
|
for _, isSet := range flagnameAndStatus {
|
||||||
|
if isSet {
|
||||||
|
// One of the flags of the group is set, mark the other ones as required
|
||||||
|
for _, fName := range strings.Split(flagList, " ") {
|
||||||
|
_ = c.MarkFlagRequired(fName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If none of the flags of a one-required group are present, we make all the flags
|
||||||
|
// of that group required so that the shell completion suggests them automatically
|
||||||
|
for flagList, flagnameAndStatus := range oneRequiredGroupStatus {
|
||||||
|
isSet := false
|
||||||
|
|
||||||
|
for _, isSet = range flagnameAndStatus {
|
||||||
|
if isSet {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// None of the flags of the group are set, mark all flags in the group
|
||||||
|
// as required
|
||||||
|
if !isSet {
|
||||||
|
for _, fName := range strings.Split(flagList, " ") {
|
||||||
|
_ = c.MarkFlagRequired(fName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a flag that is mutually exclusive to others is present, we hide the other
|
||||||
|
// flags of that group so the shell completion does not suggest them
|
||||||
|
for flagList, flagnameAndStatus := range mutuallyExclusiveGroupStatus {
|
||||||
|
for flagName, isSet := range flagnameAndStatus {
|
||||||
|
if isSet {
|
||||||
|
// One of the flags of the mutually exclusive group is set, mark the other ones as hidden
|
||||||
|
// Don't mark the flag that is already set as hidden because it may be an
|
||||||
|
// array or slice flag and therefore must continue being suggested
|
||||||
|
for _, fName := range strings.Split(flagList, " ") {
|
||||||
|
if fName != flagName {
|
||||||
|
flag := c.Flags().Lookup(fName)
|
||||||
|
flag.Hidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
350
vendor/github.com/spf13/cobra/powershell_completions.go
generated
vendored
Normal file
350
vendor/github.com/spf13/cobra/powershell_completions.go
generated
vendored
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
// Copyright 2013-2023 The Cobra Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// The generated scripts require PowerShell v5.0+ (which comes Windows 10, but
|
||||||
|
// can be downloaded separately for windows 7 or 8.1).
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func genPowerShellComp(buf io.StringWriter, name string, includeDesc bool) {
|
||||||
|
// Variables should not contain a '-' or ':' character
|
||||||
|
nameForVar := name
|
||||||
|
nameForVar = strings.ReplaceAll(nameForVar, "-", "_")
|
||||||
|
nameForVar = strings.ReplaceAll(nameForVar, ":", "_")
|
||||||
|
|
||||||
|
compCmd := ShellCompRequestCmd
|
||||||
|
if !includeDesc {
|
||||||
|
compCmd = ShellCompNoDescRequestCmd
|
||||||
|
}
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(`# powershell completion for %-36[1]s -*- shell-script -*-
|
||||||
|
|
||||||
|
function __%[1]s_debug {
|
||||||
|
if ($env:BASH_COMP_DEBUG_FILE) {
|
||||||
|
"$args" | Out-File -Append -FilePath "$env:BASH_COMP_DEBUG_FILE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filter __%[1]s_escapeStringWithSpecialChars {
|
||||||
|
`+" $_ -replace '\\s|#|@|\\$|;|,|''|\\{|\\}|\\(|\\)|\"|`|\\||<|>|&','`$&'"+`
|
||||||
|
}
|
||||||
|
|
||||||
|
[scriptblock]${__%[2]sCompleterBlock} = {
|
||||||
|
param(
|
||||||
|
$WordToComplete,
|
||||||
|
$CommandAst,
|
||||||
|
$CursorPosition
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get the current command line and convert into a string
|
||||||
|
$Command = $CommandAst.CommandElements
|
||||||
|
$Command = "$Command"
|
||||||
|
|
||||||
|
__%[1]s_debug ""
|
||||||
|
__%[1]s_debug "========= starting completion logic =========="
|
||||||
|
__%[1]s_debug "WordToComplete: $WordToComplete Command: $Command CursorPosition: $CursorPosition"
|
||||||
|
|
||||||
|
# The user could have moved the cursor backwards on the command-line.
|
||||||
|
# We need to trigger completion from the $CursorPosition location, so we need
|
||||||
|
# to truncate the command-line ($Command) up to the $CursorPosition location.
|
||||||
|
# Make sure the $Command is longer then the $CursorPosition before we truncate.
|
||||||
|
# This happens because the $Command does not include the last space.
|
||||||
|
if ($Command.Length -gt $CursorPosition) {
|
||||||
|
$Command=$Command.Substring(0,$CursorPosition)
|
||||||
|
}
|
||||||
|
__%[1]s_debug "Truncated command: $Command"
|
||||||
|
|
||||||
|
$ShellCompDirectiveError=%[4]d
|
||||||
|
$ShellCompDirectiveNoSpace=%[5]d
|
||||||
|
$ShellCompDirectiveNoFileComp=%[6]d
|
||||||
|
$ShellCompDirectiveFilterFileExt=%[7]d
|
||||||
|
$ShellCompDirectiveFilterDirs=%[8]d
|
||||||
|
$ShellCompDirectiveKeepOrder=%[9]d
|
||||||
|
|
||||||
|
# Prepare the command to request completions for the program.
|
||||||
|
# Split the command at the first space to separate the program and arguments.
|
||||||
|
$Program,$Arguments = $Command.Split(" ",2)
|
||||||
|
|
||||||
|
$RequestComp="$Program %[3]s $Arguments"
|
||||||
|
__%[1]s_debug "RequestComp: $RequestComp"
|
||||||
|
|
||||||
|
# we cannot use $WordToComplete because it
|
||||||
|
# has the wrong values if the cursor was moved
|
||||||
|
# so use the last argument
|
||||||
|
if ($WordToComplete -ne "" ) {
|
||||||
|
$WordToComplete = $Arguments.Split(" ")[-1]
|
||||||
|
}
|
||||||
|
__%[1]s_debug "New WordToComplete: $WordToComplete"
|
||||||
|
|
||||||
|
|
||||||
|
# Check for flag with equal sign
|
||||||
|
$IsEqualFlag = ($WordToComplete -Like "--*=*" )
|
||||||
|
if ( $IsEqualFlag ) {
|
||||||
|
__%[1]s_debug "Completing equal sign flag"
|
||||||
|
# Remove the flag part
|
||||||
|
$Flag,$WordToComplete = $WordToComplete.Split("=",2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $WordToComplete -eq "" -And ( -Not $IsEqualFlag )) {
|
||||||
|
# If the last parameter is complete (there is a space following it)
|
||||||
|
# We add an extra empty parameter so we can indicate this to the go method.
|
||||||
|
__%[1]s_debug "Adding extra empty parameter"
|
||||||
|
# PowerShell 7.2+ changed the way how the arguments are passed to executables,
|
||||||
|
# so for pre-7.2 or when Legacy argument passing is enabled we need to use
|
||||||
|
`+" # `\"`\" to pass an empty argument, a \"\" or '' does not work!!!"+`
|
||||||
|
if ($PSVersionTable.PsVersion -lt [version]'7.2.0' -or
|
||||||
|
($PSVersionTable.PsVersion -lt [version]'7.3.0' -and -not [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -or
|
||||||
|
(($PSVersionTable.PsVersion -ge [version]'7.3.0' -or [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -and
|
||||||
|
$PSNativeCommandArgumentPassing -eq 'Legacy')) {
|
||||||
|
`+" $RequestComp=\"$RequestComp\" + ' `\"`\"'"+`
|
||||||
|
} else {
|
||||||
|
$RequestComp="$RequestComp" + ' ""'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_debug "Calling $RequestComp"
|
||||||
|
# First disable ActiveHelp which is not supported for Powershell
|
||||||
|
${env:%[10]s}=0
|
||||||
|
|
||||||
|
#call the command store the output in $out and redirect stderr and stdout to null
|
||||||
|
# $Out is an array contains each line per element
|
||||||
|
Invoke-Expression -OutVariable out "$RequestComp" 2>&1 | Out-Null
|
||||||
|
|
||||||
|
# get directive from last line
|
||||||
|
[int]$Directive = $Out[-1].TrimStart(':')
|
||||||
|
if ($Directive -eq "") {
|
||||||
|
# There is no directive specified
|
||||||
|
$Directive = 0
|
||||||
|
}
|
||||||
|
__%[1]s_debug "The completion directive is: $Directive"
|
||||||
|
|
||||||
|
# remove directive (last element) from out
|
||||||
|
$Out = $Out | Where-Object { $_ -ne $Out[-1] }
|
||||||
|
__%[1]s_debug "The completions are: $Out"
|
||||||
|
|
||||||
|
if (($Directive -band $ShellCompDirectiveError) -ne 0 ) {
|
||||||
|
# Error code. No completion.
|
||||||
|
__%[1]s_debug "Received error from custom completion go code"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$Longest = 0
|
||||||
|
[Array]$Values = $Out | ForEach-Object {
|
||||||
|
#Split the output in name and description
|
||||||
|
`+" $Name, $Description = $_.Split(\"`t\",2)"+`
|
||||||
|
__%[1]s_debug "Name: $Name Description: $Description"
|
||||||
|
|
||||||
|
# Look for the longest completion so that we can format things nicely
|
||||||
|
if ($Longest -lt $Name.Length) {
|
||||||
|
$Longest = $Name.Length
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set the description to a one space string if there is none set.
|
||||||
|
# This is needed because the CompletionResult does not accept an empty string as argument
|
||||||
|
if (-Not $Description) {
|
||||||
|
$Description = " "
|
||||||
|
}
|
||||||
|
New-Object -TypeName PSCustomObject -Property @{
|
||||||
|
Name = "$Name"
|
||||||
|
Description = "$Description"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$Space = " "
|
||||||
|
if (($Directive -band $ShellCompDirectiveNoSpace) -ne 0 ) {
|
||||||
|
# remove the space here
|
||||||
|
__%[1]s_debug "ShellCompDirectiveNoSpace is called"
|
||||||
|
$Space = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((($Directive -band $ShellCompDirectiveFilterFileExt) -ne 0 ) -or
|
||||||
|
(($Directive -band $ShellCompDirectiveFilterDirs) -ne 0 )) {
|
||||||
|
__%[1]s_debug "ShellCompDirectiveFilterFileExt ShellCompDirectiveFilterDirs are not supported"
|
||||||
|
|
||||||
|
# return here to prevent the completion of the extensions
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$Values = $Values | Where-Object {
|
||||||
|
# filter the result
|
||||||
|
$_.Name -like "$WordToComplete*"
|
||||||
|
|
||||||
|
# Join the flag back if we have an equal sign flag
|
||||||
|
if ( $IsEqualFlag ) {
|
||||||
|
__%[1]s_debug "Join the equal sign flag back to the completion value"
|
||||||
|
$_.Name = $Flag + "=" + $_.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# we sort the values in ascending order by name if keep order isn't passed
|
||||||
|
if (($Directive -band $ShellCompDirectiveKeepOrder) -eq 0 ) {
|
||||||
|
$Values = $Values | Sort-Object -Property Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) {
|
||||||
|
__%[1]s_debug "ShellCompDirectiveNoFileComp is called"
|
||||||
|
|
||||||
|
if ($Values.Length -eq 0) {
|
||||||
|
# Just print an empty string here so the
|
||||||
|
# shell does not start to complete paths.
|
||||||
|
# We cannot use CompletionResult here because
|
||||||
|
# it does not accept an empty string as argument.
|
||||||
|
""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the current mode
|
||||||
|
$Mode = (Get-PSReadLineKeyHandler | Where-Object {$_.Key -eq "Tab" }).Function
|
||||||
|
__%[1]s_debug "Mode: $Mode"
|
||||||
|
|
||||||
|
$Values | ForEach-Object {
|
||||||
|
|
||||||
|
# store temporary because switch will overwrite $_
|
||||||
|
$comp = $_
|
||||||
|
|
||||||
|
# PowerShell supports three different completion modes
|
||||||
|
# - TabCompleteNext (default windows style - on each key press the next option is displayed)
|
||||||
|
# - Complete (works like bash)
|
||||||
|
# - MenuComplete (works like zsh)
|
||||||
|
# You set the mode with Set-PSReadLineKeyHandler -Key Tab -Function <mode>
|
||||||
|
|
||||||
|
# CompletionResult Arguments:
|
||||||
|
# 1) CompletionText text to be used as the auto completion result
|
||||||
|
# 2) ListItemText text to be displayed in the suggestion list
|
||||||
|
# 3) ResultType type of completion result
|
||||||
|
# 4) ToolTip text for the tooltip with details about the object
|
||||||
|
|
||||||
|
switch ($Mode) {
|
||||||
|
|
||||||
|
# bash like
|
||||||
|
"Complete" {
|
||||||
|
|
||||||
|
if ($Values.Length -eq 1) {
|
||||||
|
__%[1]s_debug "Only one completion left"
|
||||||
|
|
||||||
|
# insert space after value
|
||||||
|
$CompletionText = $($comp.Name | __%[1]s_escapeStringWithSpecialChars) + $Space
|
||||||
|
if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){
|
||||||
|
[System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
|
||||||
|
} else {
|
||||||
|
$CompletionText
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
# Add the proper number of spaces to align the descriptions
|
||||||
|
while($comp.Name.Length -lt $Longest) {
|
||||||
|
$comp.Name = $comp.Name + " "
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check for empty description and only add parentheses if needed
|
||||||
|
if ($($comp.Description) -eq " " ) {
|
||||||
|
$Description = ""
|
||||||
|
} else {
|
||||||
|
$Description = " ($($comp.Description))"
|
||||||
|
}
|
||||||
|
|
||||||
|
$CompletionText = "$($comp.Name)$Description"
|
||||||
|
if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){
|
||||||
|
[System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)$Description", 'ParameterValue', "$($comp.Description)")
|
||||||
|
} else {
|
||||||
|
$CompletionText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# zsh like
|
||||||
|
"MenuComplete" {
|
||||||
|
# insert space after value
|
||||||
|
# MenuComplete will automatically show the ToolTip of
|
||||||
|
# the highlighted value at the bottom of the suggestions.
|
||||||
|
|
||||||
|
$CompletionText = $($comp.Name | __%[1]s_escapeStringWithSpecialChars) + $Space
|
||||||
|
if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){
|
||||||
|
[System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
|
||||||
|
} else {
|
||||||
|
$CompletionText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# TabCompleteNext and in case we get something unknown
|
||||||
|
Default {
|
||||||
|
# Like MenuComplete but we don't want to add a space here because
|
||||||
|
# the user need to press space anyway to get the completion.
|
||||||
|
# Description will not be shown because that's not possible with TabCompleteNext
|
||||||
|
|
||||||
|
$CompletionText = $($comp.Name | __%[1]s_escapeStringWithSpecialChars)
|
||||||
|
if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){
|
||||||
|
[System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
|
||||||
|
} else {
|
||||||
|
$CompletionText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock ${__%[2]sCompleterBlock}
|
||||||
|
`, name, nameForVar, compCmd,
|
||||||
|
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
||||||
|
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpEnvVar(name)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) genPowerShellCompletion(w io.Writer, includeDesc bool) error {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
genPowerShellComp(buf, c.Name(), includeDesc)
|
||||||
|
_, err := buf.WriteTo(w)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) genPowerShellCompletionFile(filename string, includeDesc bool) error {
|
||||||
|
outFile, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outFile.Close()
|
||||||
|
|
||||||
|
return c.genPowerShellCompletion(outFile, includeDesc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenPowerShellCompletionFile generates powershell completion file without descriptions.
|
||||||
|
func (c *Command) GenPowerShellCompletionFile(filename string) error {
|
||||||
|
return c.genPowerShellCompletionFile(filename, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenPowerShellCompletion generates powershell completion file without descriptions
|
||||||
|
// and writes it to the passed writer.
|
||||||
|
func (c *Command) GenPowerShellCompletion(w io.Writer) error {
|
||||||
|
return c.genPowerShellCompletion(w, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenPowerShellCompletionFileWithDesc generates powershell completion file with descriptions.
|
||||||
|
func (c *Command) GenPowerShellCompletionFileWithDesc(filename string) error {
|
||||||
|
return c.genPowerShellCompletionFile(filename, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenPowerShellCompletionWithDesc generates powershell completion file with descriptions
|
||||||
|
// and writes it to the passed writer.
|
||||||
|
func (c *Command) GenPowerShellCompletionWithDesc(w io.Writer) error {
|
||||||
|
return c.genPowerShellCompletion(w, true)
|
||||||
|
}
|
||||||
98
vendor/github.com/spf13/cobra/shell_completions.go
generated
vendored
Normal file
98
vendor/github.com/spf13/cobra/shell_completions.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
// Copyright 2013-2023 The Cobra Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MarkFlagRequired instructs the various shell completion implementations to
|
||||||
|
// prioritize the named flag when performing completion,
|
||||||
|
// and causes your command to report an error if invoked without the flag.
|
||||||
|
func (c *Command) MarkFlagRequired(name string) error {
|
||||||
|
return MarkFlagRequired(c.Flags(), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkPersistentFlagRequired instructs the various shell completion implementations to
|
||||||
|
// prioritize the named persistent flag when performing completion,
|
||||||
|
// and causes your command to report an error if invoked without the flag.
|
||||||
|
func (c *Command) MarkPersistentFlagRequired(name string) error {
|
||||||
|
return MarkFlagRequired(c.PersistentFlags(), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkFlagRequired instructs the various shell completion implementations to
|
||||||
|
// prioritize the named flag when performing completion,
|
||||||
|
// and causes your command to report an error if invoked without the flag.
|
||||||
|
func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
|
||||||
|
return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkFlagFilename instructs the various shell completion implementations to
|
||||||
|
// limit completions for the named flag to the specified file extensions.
|
||||||
|
func (c *Command) MarkFlagFilename(name string, extensions ...string) error {
|
||||||
|
return MarkFlagFilename(c.Flags(), name, extensions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists.
|
||||||
|
// The bash completion script will call the bash function f for the flag.
|
||||||
|
//
|
||||||
|
// This will only work for bash completion.
|
||||||
|
// It is recommended to instead use c.RegisterFlagCompletionFunc(...) which allows
|
||||||
|
// to register a Go function which will work across all shells.
|
||||||
|
func (c *Command) MarkFlagCustom(name string, f string) error {
|
||||||
|
return MarkFlagCustom(c.Flags(), name, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkPersistentFlagFilename instructs the various shell completion
|
||||||
|
// implementations to limit completions for the named persistent flag to the
|
||||||
|
// specified file extensions.
|
||||||
|
func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error {
|
||||||
|
return MarkFlagFilename(c.PersistentFlags(), name, extensions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkFlagFilename instructs the various shell completion implementations to
|
||||||
|
// limit completions for the named flag to the specified file extensions.
|
||||||
|
func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error {
|
||||||
|
return flags.SetAnnotation(name, BashCompFilenameExt, extensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists.
|
||||||
|
// The bash completion script will call the bash function f for the flag.
|
||||||
|
//
|
||||||
|
// This will only work for bash completion.
|
||||||
|
// It is recommended to instead use c.RegisterFlagCompletionFunc(...) which allows
|
||||||
|
// to register a Go function which will work across all shells.
|
||||||
|
func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error {
|
||||||
|
return flags.SetAnnotation(name, BashCompCustom, []string{f})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkFlagDirname instructs the various shell completion implementations to
|
||||||
|
// limit completions for the named flag to directory names.
|
||||||
|
func (c *Command) MarkFlagDirname(name string) error {
|
||||||
|
return MarkFlagDirname(c.Flags(), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkPersistentFlagDirname instructs the various shell completion
|
||||||
|
// implementations to limit completions for the named persistent flag to
|
||||||
|
// directory names.
|
||||||
|
func (c *Command) MarkPersistentFlagDirname(name string) error {
|
||||||
|
return MarkFlagDirname(c.PersistentFlags(), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkFlagDirname instructs the various shell completion implementations to
|
||||||
|
// limit completions for the named flag to directory names.
|
||||||
|
func MarkFlagDirname(flags *pflag.FlagSet, name string) error {
|
||||||
|
return flags.SetAnnotation(name, BashCompSubdirsInDir, []string{})
|
||||||
|
}
|
||||||
308
vendor/github.com/spf13/cobra/zsh_completions.go
generated
vendored
Normal file
308
vendor/github.com/spf13/cobra/zsh_completions.go
generated
vendored
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
// Copyright 2013-2023 The Cobra Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenZshCompletionFile generates zsh completion file including descriptions.
|
||||||
|
func (c *Command) GenZshCompletionFile(filename string) error {
|
||||||
|
return c.genZshCompletionFile(filename, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenZshCompletion generates zsh completion file including descriptions
|
||||||
|
// and writes it to the passed writer.
|
||||||
|
func (c *Command) GenZshCompletion(w io.Writer) error {
|
||||||
|
return c.genZshCompletion(w, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenZshCompletionFileNoDesc generates zsh completion file without descriptions.
|
||||||
|
func (c *Command) GenZshCompletionFileNoDesc(filename string) error {
|
||||||
|
return c.genZshCompletionFile(filename, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenZshCompletionNoDesc generates zsh completion file without descriptions
|
||||||
|
// and writes it to the passed writer.
|
||||||
|
func (c *Command) GenZshCompletionNoDesc(w io.Writer) error {
|
||||||
|
return c.genZshCompletion(w, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkZshCompPositionalArgumentFile only worked for zsh and its behavior was
|
||||||
|
// not consistent with Bash completion. It has therefore been disabled.
|
||||||
|
// Instead, when no other completion is specified, file completion is done by
|
||||||
|
// default for every argument. One can disable file completion on a per-argument
|
||||||
|
// basis by using ValidArgsFunction and ShellCompDirectiveNoFileComp.
|
||||||
|
// To achieve file extension filtering, one can use ValidArgsFunction and
|
||||||
|
// ShellCompDirectiveFilterFileExt.
|
||||||
|
//
|
||||||
|
// Deprecated
|
||||||
|
func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkZshCompPositionalArgumentWords only worked for zsh. It has therefore
|
||||||
|
// been disabled.
|
||||||
|
// To achieve the same behavior across all shells, one can use
|
||||||
|
// ValidArgs (for the first argument only) or ValidArgsFunction for
|
||||||
|
// any argument (can include the first one also).
|
||||||
|
//
|
||||||
|
// Deprecated
|
||||||
|
func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) genZshCompletionFile(filename string, includeDesc bool) error {
|
||||||
|
outFile, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outFile.Close()
|
||||||
|
|
||||||
|
return c.genZshCompletion(outFile, includeDesc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) genZshCompletion(w io.Writer, includeDesc bool) error {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
genZshComp(buf, c.Name(), includeDesc)
|
||||||
|
_, err := buf.WriteTo(w)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func genZshComp(buf io.StringWriter, name string, includeDesc bool) {
|
||||||
|
compCmd := ShellCompRequestCmd
|
||||||
|
if !includeDesc {
|
||||||
|
compCmd = ShellCompNoDescRequestCmd
|
||||||
|
}
|
||||||
|
WriteStringAndCheck(buf, fmt.Sprintf(`#compdef %[1]s
|
||||||
|
compdef _%[1]s %[1]s
|
||||||
|
|
||||||
|
# zsh completion for %-36[1]s -*- shell-script -*-
|
||||||
|
|
||||||
|
__%[1]s_debug()
|
||||||
|
{
|
||||||
|
local file="$BASH_COMP_DEBUG_FILE"
|
||||||
|
if [[ -n ${file} ]]; then
|
||||||
|
echo "$*" >> "${file}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_%[1]s()
|
||||||
|
{
|
||||||
|
local shellCompDirectiveError=%[3]d
|
||||||
|
local shellCompDirectiveNoSpace=%[4]d
|
||||||
|
local shellCompDirectiveNoFileComp=%[5]d
|
||||||
|
local shellCompDirectiveFilterFileExt=%[6]d
|
||||||
|
local shellCompDirectiveFilterDirs=%[7]d
|
||||||
|
local shellCompDirectiveKeepOrder=%[8]d
|
||||||
|
|
||||||
|
local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace keepOrder
|
||||||
|
local -a completions
|
||||||
|
|
||||||
|
__%[1]s_debug "\n========= starting completion logic =========="
|
||||||
|
__%[1]s_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}"
|
||||||
|
|
||||||
|
# The user could have moved the cursor backwards on the command-line.
|
||||||
|
# We need to trigger completion from the $CURRENT location, so we need
|
||||||
|
# to truncate the command-line ($words) up to the $CURRENT location.
|
||||||
|
# (We cannot use $CURSOR as its value does not work when a command is an alias.)
|
||||||
|
words=("${=words[1,CURRENT]}")
|
||||||
|
__%[1]s_debug "Truncated words[*]: ${words[*]},"
|
||||||
|
|
||||||
|
lastParam=${words[-1]}
|
||||||
|
lastChar=${lastParam[-1]}
|
||||||
|
__%[1]s_debug "lastParam: ${lastParam}, lastChar: ${lastChar}"
|
||||||
|
|
||||||
|
# For zsh, when completing a flag with an = (e.g., %[1]s -n=<TAB>)
|
||||||
|
# completions must be prefixed with the flag
|
||||||
|
setopt local_options BASH_REMATCH
|
||||||
|
if [[ "${lastParam}" =~ '-.*=' ]]; then
|
||||||
|
# We are dealing with a flag with an =
|
||||||
|
flagPrefix="-P ${BASH_REMATCH}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prepare the command to obtain completions
|
||||||
|
requestComp="${words[1]} %[2]s ${words[2,-1]}"
|
||||||
|
if [ "${lastChar}" = "" ]; then
|
||||||
|
# If the last parameter is complete (there is a space following it)
|
||||||
|
# We add an extra empty parameter so we can indicate this to the go completion code.
|
||||||
|
__%[1]s_debug "Adding extra empty parameter"
|
||||||
|
requestComp="${requestComp} \"\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
__%[1]s_debug "About to call: eval ${requestComp}"
|
||||||
|
|
||||||
|
# Use eval to handle any environment variables and such
|
||||||
|
out=$(eval ${requestComp} 2>/dev/null)
|
||||||
|
__%[1]s_debug "completion output: ${out}"
|
||||||
|
|
||||||
|
# Extract the directive integer following a : from the last line
|
||||||
|
local lastLine
|
||||||
|
while IFS='\n' read -r line; do
|
||||||
|
lastLine=${line}
|
||||||
|
done < <(printf "%%s\n" "${out[@]}")
|
||||||
|
__%[1]s_debug "last line: ${lastLine}"
|
||||||
|
|
||||||
|
if [ "${lastLine[1]}" = : ]; then
|
||||||
|
directive=${lastLine[2,-1]}
|
||||||
|
# Remove the directive including the : and the newline
|
||||||
|
local suffix
|
||||||
|
(( suffix=${#lastLine}+2))
|
||||||
|
out=${out[1,-$suffix]}
|
||||||
|
else
|
||||||
|
# There is no directive specified. Leave $out as is.
|
||||||
|
__%[1]s_debug "No directive found. Setting do default"
|
||||||
|
directive=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
__%[1]s_debug "directive: ${directive}"
|
||||||
|
__%[1]s_debug "completions: ${out}"
|
||||||
|
__%[1]s_debug "flagPrefix: ${flagPrefix}"
|
||||||
|
|
||||||
|
if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
|
||||||
|
__%[1]s_debug "Completion received error. Ignoring completions."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local activeHelpMarker="%[9]s"
|
||||||
|
local endIndex=${#activeHelpMarker}
|
||||||
|
local startIndex=$((${#activeHelpMarker}+1))
|
||||||
|
local hasActiveHelp=0
|
||||||
|
while IFS='\n' read -r comp; do
|
||||||
|
# Check if this is an activeHelp statement (i.e., prefixed with $activeHelpMarker)
|
||||||
|
if [ "${comp[1,$endIndex]}" = "$activeHelpMarker" ];then
|
||||||
|
__%[1]s_debug "ActiveHelp found: $comp"
|
||||||
|
comp="${comp[$startIndex,-1]}"
|
||||||
|
if [ -n "$comp" ]; then
|
||||||
|
compadd -x "${comp}"
|
||||||
|
__%[1]s_debug "ActiveHelp will need delimiter"
|
||||||
|
hasActiveHelp=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$comp" ]; then
|
||||||
|
# If requested, completions are returned with a description.
|
||||||
|
# The description is preceded by a TAB character.
|
||||||
|
# For zsh's _describe, we need to use a : instead of a TAB.
|
||||||
|
# We first need to escape any : as part of the completion itself.
|
||||||
|
comp=${comp//:/\\:}
|
||||||
|
|
||||||
|
local tab="$(printf '\t')"
|
||||||
|
comp=${comp//$tab/:}
|
||||||
|
|
||||||
|
__%[1]s_debug "Adding completion: ${comp}"
|
||||||
|
completions+=${comp}
|
||||||
|
lastComp=$comp
|
||||||
|
fi
|
||||||
|
done < <(printf "%%s\n" "${out[@]}")
|
||||||
|
|
||||||
|
# Add a delimiter after the activeHelp statements, but only if:
|
||||||
|
# - there are completions following the activeHelp statements, or
|
||||||
|
# - file completion will be performed (so there will be choices after the activeHelp)
|
||||||
|
if [ $hasActiveHelp -eq 1 ]; then
|
||||||
|
if [ ${#completions} -ne 0 ] || [ $((directive & shellCompDirectiveNoFileComp)) -eq 0 ]; then
|
||||||
|
__%[1]s_debug "Adding activeHelp delimiter"
|
||||||
|
compadd -x "--"
|
||||||
|
hasActiveHelp=0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
|
||||||
|
__%[1]s_debug "Activating nospace."
|
||||||
|
noSpace="-S ''"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $((directive & shellCompDirectiveKeepOrder)) -ne 0 ]; then
|
||||||
|
__%[1]s_debug "Activating keep order."
|
||||||
|
keepOrder="-V"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
|
||||||
|
# File extension filtering
|
||||||
|
local filteringCmd
|
||||||
|
filteringCmd='_files'
|
||||||
|
for filter in ${completions[@]}; do
|
||||||
|
if [ ${filter[1]} != '*' ]; then
|
||||||
|
# zsh requires a glob pattern to do file filtering
|
||||||
|
filter="\*.$filter"
|
||||||
|
fi
|
||||||
|
filteringCmd+=" -g $filter"
|
||||||
|
done
|
||||||
|
filteringCmd+=" ${flagPrefix}"
|
||||||
|
|
||||||
|
__%[1]s_debug "File filtering command: $filteringCmd"
|
||||||
|
_arguments '*:filename:'"$filteringCmd"
|
||||||
|
elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
|
||||||
|
# File completion for directories only
|
||||||
|
local subdir
|
||||||
|
subdir="${completions[1]}"
|
||||||
|
if [ -n "$subdir" ]; then
|
||||||
|
__%[1]s_debug "Listing directories in $subdir"
|
||||||
|
pushd "${subdir}" >/dev/null 2>&1
|
||||||
|
else
|
||||||
|
__%[1]s_debug "Listing directories in ."
|
||||||
|
fi
|
||||||
|
|
||||||
|
local result
|
||||||
|
_arguments '*:dirname:_files -/'" ${flagPrefix}"
|
||||||
|
result=$?
|
||||||
|
if [ -n "$subdir" ]; then
|
||||||
|
popd >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
return $result
|
||||||
|
else
|
||||||
|
__%[1]s_debug "Calling _describe"
|
||||||
|
if eval _describe $keepOrder "completions" completions $flagPrefix $noSpace; then
|
||||||
|
__%[1]s_debug "_describe found some completions"
|
||||||
|
|
||||||
|
# Return the success of having called _describe
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
__%[1]s_debug "_describe did not find completions."
|
||||||
|
__%[1]s_debug "Checking if we should do file completion."
|
||||||
|
if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
|
||||||
|
__%[1]s_debug "deactivating file completion"
|
||||||
|
|
||||||
|
# We must return an error code here to let zsh know that there were no
|
||||||
|
# completions found by _describe; this is what will trigger other
|
||||||
|
# matching algorithms to attempt to find completions.
|
||||||
|
# For example zsh can match letters in the middle of words.
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
# Perform file completion
|
||||||
|
__%[1]s_debug "Activating file completion"
|
||||||
|
|
||||||
|
# We must return the result of this command, so it must be the
|
||||||
|
# last command, or else we must store its result to return it.
|
||||||
|
_arguments '*:filename:_files'" ${flagPrefix}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# don't run the completion function when being source-ed or eval-ed
|
||||||
|
if [ "$funcstack[1]" = "_%[1]s" ]; then
|
||||||
|
_%[1]s
|
||||||
|
fi
|
||||||
|
`, name, compCmd,
|
||||||
|
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
||||||
|
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder,
|
||||||
|
activeHelpMarker))
|
||||||
|
}
|
||||||
12
vendor/github.com/spf13/pflag/.editorconfig
generated
vendored
Normal file
12
vendor/github.com/spf13/pflag/.editorconfig
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
||||||
2
vendor/github.com/spf13/pflag/.gitignore
generated
vendored
Normal file
2
vendor/github.com/spf13/pflag/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.idea/*
|
||||||
|
|
||||||
4
vendor/github.com/spf13/pflag/.golangci.yaml
generated
vendored
Normal file
4
vendor/github.com/spf13/pflag/.golangci.yaml
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- nolintlint
|
||||||
22
vendor/github.com/spf13/pflag/.travis.yml
generated
vendored
Normal file
22
vendor/github.com/spf13/pflag/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
sudo: false
|
||||||
|
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.9.x
|
||||||
|
- 1.10.x
|
||||||
|
- 1.11.x
|
||||||
|
- tip
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get golang.org/x/lint/golint
|
||||||
|
- export PATH=$GOPATH/bin:$PATH
|
||||||
|
- go install ./...
|
||||||
|
|
||||||
|
script:
|
||||||
|
- verify/all.sh -v
|
||||||
|
- go test ./...
|
||||||
28
vendor/github.com/spf13/pflag/LICENSE
generated
vendored
Normal file
28
vendor/github.com/spf13/pflag/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
Copyright (c) 2012 Alex Ogier. All rights reserved.
|
||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* 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.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
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
|
||||||
|
OWNER 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.
|
||||||
323
vendor/github.com/spf13/pflag/README.md
generated
vendored
Normal file
323
vendor/github.com/spf13/pflag/README.md
generated
vendored
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
[](https://travis-ci.org/spf13/pflag)
|
||||||
|
[](https://goreportcard.com/report/github.com/spf13/pflag)
|
||||||
|
[](https://godoc.org/github.com/spf13/pflag)
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
pflag is a drop-in replacement for Go's flag package, implementing
|
||||||
|
POSIX/GNU-style --flags.
|
||||||
|
|
||||||
|
pflag is compatible with the [GNU extensions to the POSIX recommendations
|
||||||
|
for command-line options][1]. For a more precise description, see the
|
||||||
|
"Command-line flag syntax" section below.
|
||||||
|
|
||||||
|
[1]: http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
|
||||||
|
|
||||||
|
pflag is available under the same style of BSD license as the Go language,
|
||||||
|
which can be found in the LICENSE file.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
pflag is available using the standard `go get` command.
|
||||||
|
|
||||||
|
Install by running:
|
||||||
|
|
||||||
|
go get github.com/spf13/pflag
|
||||||
|
|
||||||
|
Run tests by running:
|
||||||
|
|
||||||
|
go test github.com/spf13/pflag
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
pflag is a drop-in replacement of Go's native flag package. If you import
|
||||||
|
pflag under the name "flag" then all code should continue to function
|
||||||
|
with no changes.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
import flag "github.com/spf13/pflag"
|
||||||
|
```
|
||||||
|
|
||||||
|
There is one exception to this: if you directly instantiate the Flag struct
|
||||||
|
there is one more field "Shorthand" that you will need to set.
|
||||||
|
Most code never instantiates this struct directly, and instead uses
|
||||||
|
functions such as String(), BoolVar(), and Var(), and is therefore
|
||||||
|
unaffected.
|
||||||
|
|
||||||
|
Define flags using flag.String(), Bool(), Int(), etc.
|
||||||
|
|
||||||
|
This declares an integer flag, -flagname, stored in the pointer ip, with type *int.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
var ip *int = flag.Int("flagname", 1234, "help message for flagname")
|
||||||
|
```
|
||||||
|
|
||||||
|
If you like, you can bind the flag to a variable using the Var() functions.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
var flagvar int
|
||||||
|
func init() {
|
||||||
|
flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or you can create custom flags that satisfy the Value interface (with
|
||||||
|
pointer receivers) and couple them to flag parsing by
|
||||||
|
|
||||||
|
``` go
|
||||||
|
flag.Var(&flagVal, "name", "help message for flagname")
|
||||||
|
```
|
||||||
|
|
||||||
|
For such flags, the default value is just the initial value of the variable.
|
||||||
|
|
||||||
|
After all flags are defined, call
|
||||||
|
|
||||||
|
``` go
|
||||||
|
flag.Parse()
|
||||||
|
```
|
||||||
|
|
||||||
|
to parse the command line into the defined flags.
|
||||||
|
|
||||||
|
Flags may then be used directly. If you're using the flags themselves,
|
||||||
|
they are all pointers; if you bind to variables, they're values.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
fmt.Println("ip has value ", *ip)
|
||||||
|
fmt.Println("flagvar has value ", flagvar)
|
||||||
|
```
|
||||||
|
|
||||||
|
There are helper functions available to get the value stored in a Flag if you have a FlagSet but find
|
||||||
|
it difficult to keep up with all of the pointers in your code.
|
||||||
|
If you have a pflag.FlagSet with a flag called 'flagname' of type int you
|
||||||
|
can use GetInt() to get the int value. But notice that 'flagname' must exist
|
||||||
|
and it must be an int. GetString("flagname") will fail.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
i, err := flagset.GetInt("flagname")
|
||||||
|
```
|
||||||
|
|
||||||
|
After parsing, the arguments after the flag are available as the
|
||||||
|
slice flag.Args() or individually as flag.Arg(i).
|
||||||
|
The arguments are indexed from 0 through flag.NArg()-1.
|
||||||
|
|
||||||
|
The pflag package also defines some new functions that are not in flag,
|
||||||
|
that give one-letter shorthands for flags. You can use these by appending
|
||||||
|
'P' to the name of any function that defines a flag.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
var ip = flag.IntP("flagname", "f", 1234, "help message")
|
||||||
|
var flagvar bool
|
||||||
|
func init() {
|
||||||
|
flag.BoolVarP(&flagvar, "boolname", "b", true, "help message")
|
||||||
|
}
|
||||||
|
flag.VarP(&flagVal, "varname", "v", "help message")
|
||||||
|
```
|
||||||
|
|
||||||
|
Shorthand letters can be used with single dashes on the command line.
|
||||||
|
Boolean shorthand flags can be combined with other shorthand flags.
|
||||||
|
|
||||||
|
The default set of command-line flags is controlled by
|
||||||
|
top-level functions. The FlagSet type allows one to define
|
||||||
|
independent sets of flags, such as to implement subcommands
|
||||||
|
in a command-line interface. The methods of FlagSet are
|
||||||
|
analogous to the top-level functions for the command-line
|
||||||
|
flag set.
|
||||||
|
|
||||||
|
## Setting no option default values for flags
|
||||||
|
|
||||||
|
After you create a flag it is possible to set the pflag.NoOptDefVal for
|
||||||
|
the given flag. Doing this changes the meaning of the flag slightly. If
|
||||||
|
a flag has a NoOptDefVal and the flag is set on the command line without
|
||||||
|
an option the flag will be set to the NoOptDefVal. For example given:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
var ip = flag.IntP("flagname", "f", 1234, "help message")
|
||||||
|
flag.Lookup("flagname").NoOptDefVal = "4321"
|
||||||
|
```
|
||||||
|
|
||||||
|
Would result in something like
|
||||||
|
|
||||||
|
| Parsed Arguments | Resulting Value |
|
||||||
|
| ------------- | ------------- |
|
||||||
|
| --flagname=1357 | ip=1357 |
|
||||||
|
| --flagname | ip=4321 |
|
||||||
|
| [nothing] | ip=1234 |
|
||||||
|
|
||||||
|
## Command line flag syntax
|
||||||
|
|
||||||
|
```
|
||||||
|
--flag // boolean flags, or flags with no option default values
|
||||||
|
--flag x // only on flags without a default value
|
||||||
|
--flag=x
|
||||||
|
```
|
||||||
|
|
||||||
|
Unlike the flag package, a single dash before an option means something
|
||||||
|
different than a double dash. Single dashes signify a series of shorthand
|
||||||
|
letters for flags. All but the last shorthand letter must be boolean flags
|
||||||
|
or a flag with a default value
|
||||||
|
|
||||||
|
```
|
||||||
|
// boolean or flags where the 'no option default value' is set
|
||||||
|
-f
|
||||||
|
-f=true
|
||||||
|
-abc
|
||||||
|
but
|
||||||
|
-b true is INVALID
|
||||||
|
|
||||||
|
// non-boolean and flags without a 'no option default value'
|
||||||
|
-n 1234
|
||||||
|
-n=1234
|
||||||
|
-n1234
|
||||||
|
|
||||||
|
// mixed
|
||||||
|
-abcs "hello"
|
||||||
|
-absd="hello"
|
||||||
|
-abcs1234
|
||||||
|
```
|
||||||
|
|
||||||
|
Flag parsing stops after the terminator "--". Unlike the flag package,
|
||||||
|
flags can be interspersed with arguments anywhere on the command line
|
||||||
|
before this terminator.
|
||||||
|
|
||||||
|
Integer flags accept 1234, 0664, 0x1234 and may be negative.
|
||||||
|
Boolean flags (in their long form) accept 1, 0, t, f, true, false,
|
||||||
|
TRUE, FALSE, True, False.
|
||||||
|
Duration flags accept any input valid for time.ParseDuration.
|
||||||
|
|
||||||
|
## Mutating or "Normalizing" Flag names
|
||||||
|
|
||||||
|
It is possible to set a custom flag name 'normalization function.' It allows flag names to be mutated both when created in the code and when used on the command line to some 'normalized' form. The 'normalized' form is used for comparison. Two examples of using the custom normalization func follow.
|
||||||
|
|
||||||
|
**Example #1**: You want -, _, and . in flags to compare the same. aka --my-flag == --my_flag == --my.flag
|
||||||
|
|
||||||
|
``` go
|
||||||
|
func wordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||||
|
from := []string{"-", "_"}
|
||||||
|
to := "."
|
||||||
|
for _, sep := range from {
|
||||||
|
name = strings.Replace(name, sep, to, -1)
|
||||||
|
}
|
||||||
|
return pflag.NormalizedName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
myFlagSet.SetNormalizeFunc(wordSepNormalizeFunc)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example #2**: You want to alias two flags. aka --old-flag-name == --new-flag-name
|
||||||
|
|
||||||
|
``` go
|
||||||
|
func aliasNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||||
|
switch name {
|
||||||
|
case "old-flag-name":
|
||||||
|
name = "new-flag-name"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return pflag.NormalizedName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
myFlagSet.SetNormalizeFunc(aliasNormalizeFunc)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deprecating a flag or its shorthand
|
||||||
|
It is possible to deprecate a flag, or just its shorthand. Deprecating a flag/shorthand hides it from help text and prints a usage message when the deprecated flag/shorthand is used.
|
||||||
|
|
||||||
|
**Example #1**: You want to deprecate a flag named "badflag" as well as inform the users what flag they should use instead.
|
||||||
|
```go
|
||||||
|
// deprecate a flag by specifying its name and a usage message
|
||||||
|
flags.MarkDeprecated("badflag", "please use --good-flag instead")
|
||||||
|
```
|
||||||
|
This hides "badflag" from help text, and prints `Flag --badflag has been deprecated, please use --good-flag instead` when "badflag" is used.
|
||||||
|
|
||||||
|
**Example #2**: You want to keep a flag name "noshorthandflag" but deprecate its shortname "n".
|
||||||
|
```go
|
||||||
|
// deprecate a flag shorthand by specifying its flag name and a usage message
|
||||||
|
flags.MarkShorthandDeprecated("noshorthandflag", "please use --noshorthandflag only")
|
||||||
|
```
|
||||||
|
This hides the shortname "n" from help text, and prints `Flag shorthand -n has been deprecated, please use --noshorthandflag only` when the shorthand "n" is used.
|
||||||
|
|
||||||
|
Note that usage message is essential here, and it should not be empty.
|
||||||
|
|
||||||
|
## Hidden flags
|
||||||
|
It is possible to mark a flag as hidden, meaning it will still function as normal, however will not show up in usage/help text.
|
||||||
|
|
||||||
|
**Example**: You have a flag named "secretFlag" that you need for internal use only and don't want it showing up in help text, or for its usage text to be available.
|
||||||
|
```go
|
||||||
|
// hide a flag by specifying its name
|
||||||
|
flags.MarkHidden("secretFlag")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Disable sorting of flags
|
||||||
|
`pflag` allows you to disable sorting of flags for help and usage message.
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
```go
|
||||||
|
flags.BoolP("verbose", "v", false, "verbose output")
|
||||||
|
flags.String("coolflag", "yeaah", "it's really cool flag")
|
||||||
|
flags.Int("usefulflag", 777, "sometimes it's very useful")
|
||||||
|
flags.SortFlags = false
|
||||||
|
flags.PrintDefaults()
|
||||||
|
```
|
||||||
|
**Output**:
|
||||||
|
```
|
||||||
|
-v, --verbose verbose output
|
||||||
|
--coolflag string it's really cool flag (default "yeaah")
|
||||||
|
--usefulflag int sometimes it's very useful (default 777)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Supporting Go flags when using pflag
|
||||||
|
In order to support flags defined using Go's `flag` package, they must be added to the `pflag` flagset. This is usually necessary
|
||||||
|
to support flags defined by third-party dependencies (e.g. `golang/glog`).
|
||||||
|
|
||||||
|
**Example**: You want to add the Go flags to the `CommandLine` flagset
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
goflag "flag"
|
||||||
|
flag "github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ip *int = flag.Int("flagname", 1234, "help message for flagname")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.CommandLine.AddGoFlagSet(goflag.CommandLine)
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using pflag with go test
|
||||||
|
`pflag` does not parse the shorthand versions of go test's built-in flags (i.e., those starting with `-test.`).
|
||||||
|
For more context, see issues [#63](https://github.com/spf13/pflag/issues/63) and [#238](https://github.com/spf13/pflag/issues/238) for more details.
|
||||||
|
|
||||||
|
For example, if you use pflag in your `TestMain` function and call `pflag.Parse()` after defining your custom flags, running a test like this:
|
||||||
|
```bash
|
||||||
|
go test /your/tests -run ^YourTest -v --your-test-pflags
|
||||||
|
```
|
||||||
|
will result in the `-v` flag being ignored. This happens because of the way pflag handles flag parsing, skipping over go test's built-in shorthand flags.
|
||||||
|
To work around this, you can use the `ParseSkippedFlags` function, which ensures that go test's flags are parsed separately using the standard flag package.
|
||||||
|
|
||||||
|
**Example**: You want to parse go test flags that are otherwise ignore by `pflag.Parse()`
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
goflag "flag"
|
||||||
|
flag "github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ip *int = flag.Int("flagname", 1234, "help message for flagname")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.CommandLine.AddGoFlagSet(goflag.CommandLine)
|
||||||
|
flag.ParseSkippedFlags(os.Args[1:], goflag.CommandLine)
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## More info
|
||||||
|
|
||||||
|
You can see the full reference documentation of the pflag package
|
||||||
|
[at godoc.org][3], or through go's standard documentation system by
|
||||||
|
running `godoc -http=:6060` and browsing to
|
||||||
|
[http://localhost:6060/pkg/github.com/spf13/pflag][2] after
|
||||||
|
installation.
|
||||||
|
|
||||||
|
[2]: http://localhost:6060/pkg/github.com/spf13/pflag
|
||||||
|
[3]: http://godoc.org/github.com/spf13/pflag
|
||||||
94
vendor/github.com/spf13/pflag/bool.go
generated
vendored
Normal file
94
vendor/github.com/spf13/pflag/bool.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package pflag
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
// optional interface to indicate boolean flags that can be
|
||||||
|
// supplied without "=value" text
|
||||||
|
type boolFlag interface {
|
||||||
|
Value
|
||||||
|
IsBoolFlag() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- bool Value
|
||||||
|
type boolValue bool
|
||||||
|
|
||||||
|
func newBoolValue(val bool, p *bool) *boolValue {
|
||||||
|
*p = val
|
||||||
|
return (*boolValue)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolValue) Set(s string) error {
|
||||||
|
v, err := strconv.ParseBool(s)
|
||||||
|
*b = boolValue(v)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolValue) Type() string {
|
||||||
|
return "bool"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolValue) String() string { return strconv.FormatBool(bool(*b)) }
|
||||||
|
|
||||||
|
func (b *boolValue) IsBoolFlag() bool { return true }
|
||||||
|
|
||||||
|
func boolConv(sval string) (interface{}, error) {
|
||||||
|
return strconv.ParseBool(sval)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBool return the bool value of a flag with the given name
|
||||||
|
func (f *FlagSet) GetBool(name string) (bool, error) {
|
||||||
|
val, err := f.getFlagType(name, "bool", boolConv)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return val.(bool), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolVar defines a bool flag with specified name, default value, and usage string.
|
||||||
|
// The argument p points to a bool variable in which to store the value of the flag.
|
||||||
|
func (f *FlagSet) BoolVar(p *bool, name string, value bool, usage string) {
|
||||||
|
f.BoolVarP(p, name, "", value, usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolVarP is like BoolVar, but accepts a shorthand letter that can be used after a single dash.
|
||||||
|
func (f *FlagSet) BoolVarP(p *bool, name, shorthand string, value bool, usage string) {
|
||||||
|
flag := f.VarPF(newBoolValue(value, p), name, shorthand, usage)
|
||||||
|
flag.NoOptDefVal = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolVar defines a bool flag with specified name, default value, and usage string.
|
||||||
|
// The argument p points to a bool variable in which to store the value of the flag.
|
||||||
|
func BoolVar(p *bool, name string, value bool, usage string) {
|
||||||
|
BoolVarP(p, name, "", value, usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolVarP is like BoolVar, but accepts a shorthand letter that can be used after a single dash.
|
||||||
|
func BoolVarP(p *bool, name, shorthand string, value bool, usage string) {
|
||||||
|
flag := CommandLine.VarPF(newBoolValue(value, p), name, shorthand, usage)
|
||||||
|
flag.NoOptDefVal = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool defines a bool flag with specified name, default value, and usage string.
|
||||||
|
// The return value is the address of a bool variable that stores the value of the flag.
|
||||||
|
func (f *FlagSet) Bool(name string, value bool, usage string) *bool {
|
||||||
|
return f.BoolP(name, "", value, usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolP is like Bool, but accepts a shorthand letter that can be used after a single dash.
|
||||||
|
func (f *FlagSet) BoolP(name, shorthand string, value bool, usage string) *bool {
|
||||||
|
p := new(bool)
|
||||||
|
f.BoolVarP(p, name, shorthand, value, usage)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool defines a bool flag with specified name, default value, and usage string.
|
||||||
|
// The return value is the address of a bool variable that stores the value of the flag.
|
||||||
|
func Bool(name string, value bool, usage string) *bool {
|
||||||
|
return BoolP(name, "", value, usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolP is like Bool, but accepts a shorthand letter that can be used after a single dash.
|
||||||
|
func BoolP(name, shorthand string, value bool, usage string) *bool {
|
||||||
|
b := CommandLine.BoolP(name, shorthand, value, usage)
|
||||||
|
return b
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user