Skip to content

Commit cd7fc0c

Browse files
authored
CLI rewiring using urfave/cli/v3 and new clip to-host sub command (#5)
The cli UX has been rewired using `urfave/cli/v3`, to simplify code and provide a more well rounded CLI experience. The `clipboard` now has a new `to-host` subcommand to enable copying of clipboard between profiles and the host.
2 parents 6a5a639 + 7acc694 commit cd7fc0c

36 files changed

+519
-1574
lines changed

.goreleaser.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ project_name: qubesome
22
builds:
33
- id: qubesome
44
binary: qubesome
5-
main: main.go
5+
main: cmd/qubesome/main.go
66
flags:
77
- -trimpath
88
ldflags:
99
- -s -w
10-
- -X github.com/qubesome/cli/cmd/version.version={{.Version}}
10+
- -X github.com/qubesome/cli/cmd/cli.version={{.Version}}
1111
goos:
1212
- linux
1313
goarch:

Makefile

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1+
include hack/base.mk
2+
13
TARGET_BIN ?= build/bin/qubesome
24

3-
include hack/base.mk
5+
GO_TAGS = -tags 'netgo,osusergo,static_build'
6+
LDFLAGS = -ldflags '-extldflags -static -s -w -X \
7+
github.com/qubesome/cli/cmd/cli.version=$(VERSION)'
48

59
.PHONY: help
610
help: ## display Makefile's help.
711
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
812

913
.PHONY: build
10-
build: ## build qubesome to the path set on TARGET_BIN.
11-
go build -trimpath -tags 'netgo,osusergo,static_build' -ldflags '-extldflags -static -s -w' -o $(TARGET_BIN) cmd/qubesome/main.go
14+
build: ## build qubesome to the path set by TARGET_BIN.
15+
go build -trimpath $(GO_TAGS) $(LDFLAGS) -o $(TARGET_BIN) cmd/qubesome/main.go
1216

1317
.PHONY: test
1418
test: ## run golang tests.

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,19 @@ qubesome start -git https://github.com/qubesome/sample-dotfiles -local <local_gi
5555

5656
Copy clipboard from the host to the i3 profile:
5757
```
58-
qubesome clipboard --from-host i3
58+
qubesome clip from-host i3
59+
```
60+
61+
Copy clipboard from the i3 profile to the host:
62+
```
63+
qubesome clip to-host i3
5964
```
6065

6166
#### Available Commands
6267

6368
- `qubesome start`: Start a qubesome environment for a given profile.
6469
- `qubesome run`: Run qubesome workloads.
65-
- `qubesome clipboard`: Manage the images within your workloads.
70+
- `qubesome clip`: Manage the images within your workloads.
6671
- `qubesome images`: Manage the images within your workloads.
6772
- `qubesome xdg`: Handle xdg-open based via qubesome.
6873

cmd/cli/clipboard.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/qubesome/cli/internal/clipboard"
8+
"github.com/qubesome/cli/internal/command"
9+
"github.com/urfave/cli/v3"
10+
)
11+
12+
func clipboardCommand() *cli.Command {
13+
clipType := &cli.StringFlag{
14+
Name: "type",
15+
Aliases: []string{"t"},
16+
Validator: func(s string) error {
17+
switch s {
18+
case "image/png":
19+
return nil
20+
}
21+
return fmt.Errorf("unsupported type %q", s)
22+
},
23+
}
24+
25+
cmd := &cli.Command{
26+
Name: "clipboard",
27+
Aliases: []string{"clip"},
28+
Usage: "enable sharing of clipboard across profiles and the host",
29+
Commands: []*cli.Command{
30+
{
31+
Name: "from-host",
32+
Usage: "copies the clipboard contents from the host to a profile",
33+
Arguments: []cli.Argument{
34+
&cli.StringArg{
35+
Name: "target_profile",
36+
Min: 1,
37+
Max: 1,
38+
Destination: &targetProfile,
39+
},
40+
},
41+
Flags: []cli.Flag{
42+
clipType,
43+
},
44+
Action: func(ctx context.Context, c *cli.Command) error {
45+
cfg := profileConfigOrDefault(targetProfile)
46+
47+
target, ok := cfg.Profiles[targetProfile]
48+
if !ok {
49+
return fmt.Errorf("no active profile %q found", targetProfile)
50+
}
51+
52+
opts := []command.Option[clipboard.Options]{
53+
clipboard.WithFromHost(),
54+
clipboard.WithTargetProfile(target),
55+
}
56+
57+
if typ := c.String("type"); typ != "" {
58+
fmt.Println(typ)
59+
opts = append(opts, clipboard.WithContentType(typ))
60+
}
61+
62+
return clipboard.Run(
63+
opts...,
64+
)
65+
},
66+
},
67+
{
68+
Name: "from-profile",
69+
Usage: "copies the clipboard contents between profiles",
70+
Arguments: []cli.Argument{
71+
&cli.StringArg{
72+
Name: "source_profile",
73+
Min: 1,
74+
Max: 1,
75+
Destination: &sourceProfile,
76+
},
77+
&cli.StringArg{
78+
Name: "target_profile",
79+
Min: 1,
80+
Max: 1,
81+
Destination: &targetProfile,
82+
},
83+
},
84+
Flags: []cli.Flag{
85+
clipType,
86+
},
87+
Action: func(ctx context.Context, c *cli.Command) error {
88+
cfg := profileConfigOrDefault(targetProfile)
89+
90+
source, ok := cfg.Profiles[sourceProfile]
91+
if !ok {
92+
return fmt.Errorf("no active profile %q found", sourceProfile)
93+
}
94+
95+
target, ok := cfg.Profiles[targetProfile]
96+
if !ok {
97+
return fmt.Errorf("no active profile %q found", targetProfile)
98+
}
99+
100+
opts := []command.Option[clipboard.Options]{
101+
clipboard.WithSourceProfile(source),
102+
clipboard.WithTargetProfile(target),
103+
}
104+
105+
if typ := c.String("type"); typ != "" {
106+
fmt.Println(typ)
107+
opts = append(opts, clipboard.WithContentType(typ))
108+
}
109+
110+
return clipboard.Run(
111+
opts...,
112+
)
113+
},
114+
},
115+
{
116+
Name: "to-host",
117+
Usage: "copies the clipboard contents from a profile to the host",
118+
Arguments: []cli.Argument{
119+
&cli.StringArg{
120+
Name: "source_profile",
121+
Min: 1,
122+
Max: 1,
123+
Destination: &sourceProfile,
124+
},
125+
},
126+
Flags: []cli.Flag{
127+
clipType,
128+
},
129+
Action: func(ctx context.Context, c *cli.Command) error {
130+
cfg := profileConfigOrDefault(sourceProfile)
131+
132+
target, ok := cfg.Profiles[sourceProfile]
133+
if !ok {
134+
return fmt.Errorf("no active profile %q found", sourceProfile)
135+
}
136+
137+
opts := []command.Option[clipboard.Options]{
138+
clipboard.WithSourceProfile(target),
139+
clipboard.WithTargetHost(),
140+
}
141+
142+
if typ := c.String("type"); typ != "" {
143+
fmt.Println(typ)
144+
opts = append(opts, clipboard.WithContentType(typ))
145+
}
146+
147+
return clipboard.Run(
148+
opts...,
149+
)
150+
},
151+
},
152+
},
153+
}
154+
return cmd
155+
}

cmd/cli/deps.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
6+
"github.com/qubesome/cli/internal/deps"
7+
"github.com/urfave/cli/v3"
8+
)
9+
10+
func depsCommand() *cli.Command {
11+
cmd := &cli.Command{
12+
Name: "deps",
13+
Usage: "shows status of external dependencies",
14+
Action: func(ctx context.Context, cmd *cli.Command) error {
15+
return deps.Run()
16+
},
17+
}
18+
return cmd
19+
}

cmd/cli/images.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
6+
"github.com/qubesome/cli/internal/images"
7+
"github.com/urfave/cli/v3"
8+
)
9+
10+
func imagesCommand() *cli.Command {
11+
cmd := &cli.Command{
12+
Name: "images",
13+
Aliases: []string{"i"},
14+
Usage: "manage workload images",
15+
Commands: []*cli.Command{
16+
{
17+
Name: "pull",
18+
Flags: []cli.Flag{
19+
&cli.StringFlag{
20+
Name: "profile",
21+
Destination: &targetProfile,
22+
},
23+
},
24+
Action: func(ctx context.Context, cmd *cli.Command) error {
25+
cfg := profileConfigOrDefault(targetProfile)
26+
27+
return images.Run(images.WithConfig(cfg))
28+
},
29+
},
30+
},
31+
}
32+
return cmd
33+
}

cmd/cli/root.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
"os"
6+
"path/filepath"
7+
8+
"github.com/qubesome/cli/internal/files"
9+
"github.com/qubesome/cli/internal/log"
10+
"github.com/qubesome/cli/internal/types"
11+
"github.com/urfave/cli/v3"
12+
)
13+
14+
var (
15+
targetProfile string
16+
sourceProfile string
17+
gitURL string
18+
workload string
19+
path string
20+
local string
21+
debug bool
22+
)
23+
24+
func RootCommand() *cli.Command {
25+
cmd := &cli.Command{
26+
Commands: []*cli.Command{
27+
startCommand(),
28+
runCommand(),
29+
imagesCommand(),
30+
clipboardCommand(),
31+
xdgCommand(),
32+
depsCommand(),
33+
versionCommand(),
34+
},
35+
}
36+
37+
cmd.Flags = append(cmd.Flags, &cli.BoolFlag{
38+
Name: "debug",
39+
Value: false,
40+
Destination: &debug,
41+
Sources: cli.EnvVars("QS_DEBUG"),
42+
Action: func(ctx context.Context, c *cli.Command, b bool) error {
43+
if debug {
44+
return log.Configure("DEBUG", true, false, false)
45+
}
46+
return nil
47+
},
48+
})
49+
cmd.Version = shortVersion()
50+
cmd.Usage = "A cli to GitOps your dotfiles"
51+
cmd.Suggest = true
52+
cmd.EnableShellCompletion = true
53+
54+
return cmd
55+
}
56+
57+
func config(path string) *types.Config {
58+
if _, err := os.Stat(path); err != nil {
59+
return nil
60+
}
61+
cfg, err := types.LoadConfig(path)
62+
if err != nil {
63+
return nil
64+
}
65+
cfg.RootDir = filepath.Dir(path)
66+
67+
return cfg
68+
}
69+
70+
func profileConfigOrDefault(profile string) *types.Config {
71+
path := files.ProfileConfig(profile)
72+
target, err := os.Readlink(path)
73+
74+
var c *types.Config
75+
if err == nil {
76+
c = config(target)
77+
}
78+
79+
if c != nil {
80+
return c
81+
}
82+
83+
path = files.QubesomeConfig()
84+
return config(path)
85+
}

cmd/cli/run.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
6+
"github.com/qubesome/cli/internal/qubesome"
7+
"github.com/urfave/cli/v3"
8+
)
9+
10+
func runCommand() *cli.Command {
11+
cmd := &cli.Command{
12+
Name: "run",
13+
Aliases: []string{"r"},
14+
Arguments: []cli.Argument{
15+
&cli.StringArg{
16+
Name: "workload",
17+
Min: 1,
18+
Max: 1,
19+
Destination: &workload,
20+
},
21+
},
22+
Flags: []cli.Flag{
23+
&cli.StringFlag{
24+
Name: "profile",
25+
Destination: &targetProfile,
26+
},
27+
},
28+
Usage: "execute workloads",
29+
Action: func(ctx context.Context, cmd *cli.Command) error {
30+
cfg := profileConfigOrDefault(targetProfile)
31+
32+
return qubesome.Run(
33+
qubesome.WithWorkload(workload),
34+
qubesome.WithProfile(targetProfile),
35+
qubesome.WithConfig(cfg),
36+
qubesome.WithExtraArgs(cmd.Args().Slice()),
37+
)
38+
},
39+
}
40+
return cmd
41+
}

0 commit comments

Comments
 (0)