|
1 | 1 | package main |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "context" |
5 | | - "errors" |
6 | 4 | "os" |
7 | | - "os/signal" |
8 | | - "strings" |
9 | | - "syscall" |
10 | 5 |
|
11 | | - "github.com/lets-cli/lets/internal/cmd" |
12 | | - "github.com/lets-cli/lets/internal/config" |
13 | | - "github.com/lets-cli/lets/internal/env" |
14 | | - "github.com/lets-cli/lets/internal/executor" |
15 | | - "github.com/lets-cli/lets/internal/logging" |
16 | | - "github.com/lets-cli/lets/internal/set" |
17 | | - "github.com/lets-cli/lets/internal/upgrade" |
18 | | - "github.com/lets-cli/lets/internal/upgrade/registry" |
19 | | - "github.com/lets-cli/lets/internal/workdir" |
20 | | - log "github.com/sirupsen/logrus" |
21 | | - "github.com/spf13/cobra" |
| 6 | + "github.com/lets-cli/lets/internal/cli" |
22 | 7 | ) |
23 | 8 |
|
24 | 9 | var Version = "0.0.0-dev" |
25 | 10 | var BuildDate = "" |
26 | 11 |
|
27 | 12 | func main() { |
28 | | - ctx := getContext() |
29 | | - |
30 | | - configDir := os.Getenv("LETS_CONFIG_DIR") |
31 | | - |
32 | | - logging.InitLogging(os.Stdout, os.Stderr) |
33 | | - |
34 | | - rootCmd := cmd.CreateRootCommand(Version, BuildDate) |
35 | | - rootCmd.InitDefaultHelpFlag() |
36 | | - rootCmd.InitDefaultVersionFlag() |
37 | | - reinitCompletionCmd := cmd.InitCompletionCmd(rootCmd, nil) |
38 | | - cmd.InitSelfCmd(rootCmd, Version) |
39 | | - rootCmd.InitDefaultHelpCmd() |
40 | | - |
41 | | - command, args, err := rootCmd.Traverse(os.Args[1:]) |
42 | | - if err != nil { |
43 | | - log.Errorf("lets: traverse commands error: %s", err) |
44 | | - os.Exit(getExitCode(err, 1)) |
45 | | - } |
46 | | - |
47 | | - rootFlags, err := parseRootFlags(args) |
48 | | - if err != nil { |
49 | | - log.Errorf("lets: parse flags error: %s", err) |
50 | | - os.Exit(1) |
51 | | - } |
52 | | - |
53 | | - if rootFlags.version { |
54 | | - if err := cmd.PrintVersionMessage(rootCmd); err != nil { |
55 | | - log.Errorf("lets: print version error: %s", err) |
56 | | - os.Exit(1) |
57 | | - } |
58 | | - |
59 | | - os.Exit(0) |
60 | | - } |
61 | | - |
62 | | - debugLevel := env.SetDebugLevel(rootFlags.debug) |
63 | | - |
64 | | - if debugLevel > 0 { |
65 | | - log.SetLevel(log.DebugLevel) |
66 | | - } |
67 | | - |
68 | | - if rootFlags.config == "" { |
69 | | - rootFlags.config = os.Getenv("LETS_CONFIG") |
70 | | - } |
71 | | - |
72 | | - cfg, err := config.Load(rootFlags.config, configDir, Version) |
73 | | - if err != nil { |
74 | | - if failOnConfigError(rootCmd, command, rootFlags) { |
75 | | - log.Errorf("lets: config error: %s", err) |
76 | | - os.Exit(1) |
77 | | - } |
78 | | - } |
79 | | - |
80 | | - if cfg != nil { |
81 | | - reinitCompletionCmd(cfg) |
82 | | - cmd.InitSubCommands(rootCmd, cfg, rootFlags.all, os.Stdout) |
83 | | - } |
84 | | - |
85 | | - if rootFlags.init { |
86 | | - wd, err := os.Getwd() |
87 | | - if err == nil { |
88 | | - err = workdir.InitLetsFile(wd, Version) |
89 | | - } |
90 | | - |
91 | | - if err != nil { |
92 | | - log.Errorf("lets: can not create lets.yaml: %s", err) |
93 | | - os.Exit(1) |
94 | | - } |
95 | | - |
96 | | - os.Exit(0) |
97 | | - } |
98 | | - |
99 | | - if rootFlags.upgrade { |
100 | | - upgrader, err := upgrade.NewBinaryUpgrader(registry.NewGithubRegistry(ctx), Version) |
101 | | - if err == nil { |
102 | | - err = upgrader.Upgrade() |
103 | | - } |
104 | | - |
105 | | - if err != nil { |
106 | | - log.Errorf("lets: can not self-upgrade binary: %s", err) |
107 | | - os.Exit(1) |
108 | | - } |
109 | | - |
110 | | - os.Exit(0) |
111 | | - } |
112 | | - |
113 | | - showUsage := rootFlags.help || (command.Name() == "help" && len(args) == 0) || (len(os.Args) == 1) |
114 | | - |
115 | | - if showUsage { |
116 | | - if err := cmd.PrintRootHelpMessage(rootCmd); err != nil { |
117 | | - log.Errorf("lets: print help error: %s", err) |
118 | | - os.Exit(1) |
119 | | - } |
120 | | - |
121 | | - os.Exit(0) |
122 | | - } |
123 | | - |
124 | | - if err := rootCmd.ExecuteContext(ctx); err != nil { |
125 | | - var depErr *executor.DependencyError |
126 | | - if errors.As(err, &depErr) { |
127 | | - executor.PrintDependencyTree(depErr, os.Stderr) |
128 | | - } |
129 | | - |
130 | | - log.Errorf("lets: %s", err.Error()) |
131 | | - os.Exit(getExitCode(err, 1)) |
132 | | - } |
133 | | -} |
134 | | - |
135 | | -// getContext returns context and kicks of a goroutine |
136 | | -// which waits for SIGINT, SIGTERM and cancels global context. |
137 | | -// |
138 | | -// Note that since we setting stdin to command we run, that command |
139 | | -// will receive SIGINT, SIGTERM at the same time as we here, |
140 | | -// so command's process can begin finishing earlier than cancel will say it to. |
141 | | -func getContext() context.Context { |
142 | | - ch := make(chan os.Signal, 1) |
143 | | - signal.Notify(ch, os.Interrupt, syscall.SIGTERM) |
144 | | - |
145 | | - ctx, cancel := context.WithCancel(context.Background()) |
146 | | - |
147 | | - go func() { |
148 | | - sig := <-ch |
149 | | - log.Printf("lets: signal received: %s", sig) |
150 | | - cancel() |
151 | | - }() |
152 | | - |
153 | | - return ctx |
154 | | -} |
155 | | - |
156 | | -func getExitCode(err error, defaultCode int) int { |
157 | | - var exitCoder interface{ ExitCode() int } |
158 | | - if errors.As(err, &exitCoder) { |
159 | | - return exitCoder.ExitCode() |
160 | | - } |
161 | | - |
162 | | - return defaultCode |
163 | | -} |
164 | | - |
165 | | -// do not fail on config error in it is help (-h, --help) or --init or completion command. |
166 | | -func failOnConfigError(root *cobra.Command, current *cobra.Command, rootFlags *flags) bool { |
167 | | - rootCommands := set.NewSet("completion", "help", "lsp") |
168 | | - return (root.Flags().NFlag() == 0 && !rootCommands.Contains(current.Name())) && !rootFlags.help && !rootFlags.init |
169 | | -} |
170 | | - |
171 | | -type flags struct { |
172 | | - config string |
173 | | - debug int |
174 | | - help bool |
175 | | - version bool |
176 | | - all bool |
177 | | - init bool |
178 | | - upgrade bool |
179 | | -} |
180 | | - |
181 | | -// We can not parse --config and --debug flags using cobra.Command.ParseFlags |
182 | | -// |
183 | | -// until we read config and initialize all subcommands. |
184 | | -// Otherwise root command will parse all flags gready. |
185 | | -// |
186 | | -// For example in 'lets --config lets.my.yaml mysubcommand --config=myconfig' |
187 | | -// |
188 | | -// cobra will parse all --config flags, but take only latest |
189 | | -// |
190 | | -// --config=myconfig, and this is wrong. |
191 | | -func parseRootFlags(args []string) (*flags, error) { |
192 | | - f := &flags{} |
193 | | - // if first arg is not a flag, then it is subcommand |
194 | | - if len(args) > 0 && !strings.HasPrefix(args[0], "-") { |
195 | | - return f, nil |
196 | | - } |
197 | | - |
198 | | - visited := set.NewSet[string]() |
199 | | - |
200 | | - isFlagVisited := func(name string) bool { |
201 | | - if visited.Contains(name) { |
202 | | - return true |
203 | | - } |
204 | | - |
205 | | - visited.Add(name) |
206 | | - |
207 | | - return false |
208 | | - } |
209 | | - |
210 | | - idx := 0 |
211 | | - for idx < len(args) { |
212 | | - arg := args[idx] |
213 | | - if !strings.HasPrefix(arg, "-") { |
214 | | - // stop if arg is not a flag, it is probably a subcommand |
215 | | - break |
216 | | - } |
217 | | - |
218 | | - name, value, found := strings.Cut(arg, "=") |
219 | | - switch name { |
220 | | - case "--config", "-c": |
221 | | - if !isFlagVisited("config") { |
222 | | - if found { |
223 | | - if value == "" { |
224 | | - return nil, errors.New("--config must be set to value") |
225 | | - } |
226 | | - |
227 | | - f.config = value |
228 | | - } else if len(args[idx:]) > 0 { |
229 | | - f.config = args[idx+1] |
230 | | - idx += 2 |
231 | | - |
232 | | - continue |
233 | | - } |
234 | | - } |
235 | | - case "--debug", "-d", "-dd": |
236 | | - if !isFlagVisited("debug") { |
237 | | - f.debug = 1 |
238 | | - if arg == "-dd" { |
239 | | - f.debug = 2 |
240 | | - } |
241 | | - } |
242 | | - case "--help", "-h": |
243 | | - if !isFlagVisited("help") { |
244 | | - f.help = true |
245 | | - } |
246 | | - case "--version", "-v": |
247 | | - if !isFlagVisited("version") { |
248 | | - f.version = true |
249 | | - } |
250 | | - case "--all": |
251 | | - if !isFlagVisited("all") { |
252 | | - f.all = true |
253 | | - } |
254 | | - case "--init": |
255 | | - if !isFlagVisited("init") { |
256 | | - f.init = true |
257 | | - } |
258 | | - case "--upgrade": |
259 | | - if !isFlagVisited("upgrade") { |
260 | | - f.upgrade = true |
261 | | - } |
262 | | - } |
263 | | - |
264 | | - idx += 1 //nolint:revive,golint |
265 | | - } |
266 | | - |
267 | | - return f, nil |
| 13 | + os.Exit(cli.Main(Version, BuildDate)) |
268 | 14 | } |
0 commit comments