aboutsummaryrefslogtreecommitdiff
path: root/internal/cli/cli.go
blob: 63590af050dcdd464c082237cb0ae52f3d866795 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
package cli

import (
	"errors"
	"fmt"
	"os"
	"strings"

	"github.com/ebisu/mugi/internal/remote"
)

type CommandType int

const (
	CommandOperation CommandType = iota
	CommandAdd
	CommandRemove
	CommandList
)

type Command struct {
	Type       CommandType
	Operation  remote.Operation
	Repo       string
	Remotes    []string
	Path       string
	ConfigPath string
	Verbose    bool
	Force      bool
	Linear     bool
	Help       bool
	Version    bool
}

var ErrUnknownCommand = errors.New("unknown command")

func Parse(args []string) (Command, error) {
	cmd := Command{
		Remotes: []string{remote.All},
	}

	if len(args) == 0 {
		cmd.Help = true

		return cmd, nil
	}

	args, cmd.ConfigPath = extractConfigFlag(args)
	args, cmd.Verbose = extractVerboseFlag(args)
	args, cmd.Force = extractForceFlag(args)
	args, cmd.Linear = extractLinearFlag(args)

	for _, arg := range args {
		if arg == "-h" || arg == "--help" || arg == "help" {
			cmd.Help = true

			return cmd, nil
		}

		if arg == "-v" || arg == "--version" || arg == "version" {
			cmd.Version = true

			return cmd, nil
		}
	}

	if len(args) == 0 {
		cmd.Help = true

		return cmd, nil
	}

	switch args[0] {
	case "pull":
		cmd.Type = CommandOperation
		cmd.Operation = remote.Pull
	case "push":
		cmd.Type = CommandOperation
		cmd.Operation = remote.Push
	case "fetch":
		cmd.Type = CommandOperation
		cmd.Operation = remote.Fetch
	case "add":
		cmd.Type = CommandAdd

		if len(args) < 2 {
			cmd.Path = "."
		} else {
			cmd.Path = args[1]
		}

		return cmd, nil
	case "rm", "remove":
		cmd.Type = CommandRemove

		if len(args) < 2 {
			return cmd, fmt.Errorf("rm requires a repository name")
		}

		cmd.Repo = args[1]

		return cmd, nil
	case "list", "ls":
		cmd.Type = CommandList

		return cmd, nil
	default:
		return cmd, fmt.Errorf("%w: %s", ErrUnknownCommand, args[0])
	}

	remaining := args[1:]

	if len(remaining) == 0 {
		cmd.Repo = remote.All

		return cmd, nil
	}

	cmd.Repo = remaining[0]
	remaining = remaining[1:]

	if len(remaining) > 0 {
		cmd.Remotes = remaining
	}

	return cmd, nil
}

func Usage() string {
	return `Mugi - Personal Multi-Git Remote Manager

Usage:
  mugi [flags] <command> [repo] [remotes...]

Commands:
  pull          Pull from remote(s)
  push          Push to remote(s)
  fetch         Fetch from remote(s)
  add <path>    Add repository to config
  rm <name>     Remove repository from config
  list          List tracked repositories
  help          Show this help
  version       Show version

Flags:
  -c, --config <path>  Override config file path
  -V, --verbose        Show detailed output
  -f, --force          Force push (use with caution)
  -l, --linear         Run operations sequentially

Examples:
  mugi pull                      Pull all repositories from all remotes
  mugi push windmark gh cb       Push to GitHub and Codeberg
  mugi add .                     Add current directory to config
  mugi add ~/Developer/mugi      Add repository at path
  mugi rm mugi                   Remove repository from config
  mugi list                      List all tracked repositories

Config: ` + configPath()
}

func configPath() string {
	xdg := os.Getenv("XDG_CONFIG_HOME")

	if xdg == "" {
		home, _ := os.UserHomeDir()

		return home + "/.config/mugi/config.yaml"
	}

	return xdg + "/mugi/config.yaml"
}

func extractConfigFlag(args []string) ([]string, string) {
	var remaining []string
	var configPath string

	for i := 0; i < len(args); i++ {
		arg := args[i]

		if arg == "-c" || arg == "--config" {
			if i+1 < len(args) {
				configPath = args[i+1]
				i++
			}

			continue
		}

		if v, ok := strings.CutPrefix(arg, "--config="); ok {
			configPath = v

			continue
		}

		if v, ok := strings.CutPrefix(arg, "-c"); ok && v != "" {
			configPath = v

			continue
		}

		remaining = append(remaining, arg)
	}

	return remaining, configPath
}

func extractVerboseFlag(args []string) ([]string, bool) {
	var remaining []string
	var verbose bool

	for _, arg := range args {
		if arg == "-V" || arg == "--verbose" {
			verbose = true

			continue
		}

		remaining = append(remaining, arg)
	}

	return remaining, verbose
}

func extractForceFlag(args []string) ([]string, bool) {
	var remaining []string
	var force bool

	for _, arg := range args {
		if arg == "-f" || arg == "--force" {
			force = true

			continue
		}

		remaining = append(remaining, arg)
	}

	return remaining, force
}

func extractLinearFlag(args []string) ([]string, bool) {
	var remaining []string
	var linear bool

	for _, arg := range args {
		if arg == "-l" || arg == "--linear" {
			linear = true

			continue
		}

		remaining = append(remaining, arg)
	}

	return remaining, linear
}