diff --git a/README.md b/README.md index 32ed548..614c397 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,36 @@ # CMDR -CMDR is a simple command version manager, easy to manage and switch multiple commands. +CMDR is a simple command version management tool that helps you quickly switch from multiple command versions. ## Installation -1. Download the pre-compiled binaries from [releases](https://github.com/MrLYC/cmdr/releases). -2. Decompress the tar.gz file. -3. In order to setup the cmdr environment, run: `cmdr setup`. \ No newline at end of file +Download the latest release from [GitHub](https://github.com/MrLYC/cmdr/releases) and make sure it is executable. +Run the following command to install it in your system: +```shell +% /path/to/cmdr setup +``` + +Check the CMDR version information by running the following command: +```shell +% cmdr version -a +``` + +## Get Started +To install a new command, run the following command: +```shell +% cmdr command install -n -v -l +``` + +Then you can list the installed commands by running the following command: +```shell +% cmdr command list -n +``` + +Use a specified command version: +```shell +% cmdr command use -n -v +``` + +## Upgrade +To upgrade the CMDR, run: +```shell +% cmdr upgrade +``` \ No newline at end of file diff --git a/cmd/command/remove.go b/cmd/command/uninstall.go similarity index 100% rename from cmd/command/remove.go rename to cmd/command/uninstall.go diff --git a/cmd/setup.go b/cmd/setup.go index 2e18139..f40fd34 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -12,7 +12,8 @@ import ( ) var setupCmdFlag struct { - doNotInstall bool + doNotInstall bool + doNotWriteProfile bool } // setupCmd represents the setup command @@ -27,6 +28,7 @@ var setupCmd = &cobra.Command{ }), core.NewDBClientMaker(), core.NewDBMigrator(new(model.Command)), + core.NewShellProfiler(os.Getenv("SHELL")), ) cmdrLocation, err := os.Executable() @@ -39,6 +41,12 @@ var setupCmd = &cobra.Command{ ) } + if !setupCmdFlag.doNotWriteProfile { + runner.Add( + core.NewShellProfiler(os.Getenv("SHELL")), + ) + } + utils.ExitWithError(runner.Run(utils.SetIntoContext(cmd.Context(), map[define.ContextKey]interface{}{ define.ContextKeyName: define.Name, define.ContextKeyVersion: define.Version, @@ -48,9 +56,10 @@ var setupCmd = &cobra.Command{ }, } -func setup() { +func init() { rootCmd.AddCommand(setupCmd) flags := setupCmd.Flags() - flags.BoolVar(&setupCmdFlag.doNotInstall, "do-not-install-cmdr", false, "do not install cmdr") + flags.BoolVar(&setupCmdFlag.doNotInstall, "skip-install", false, "do not install cmdr") + flags.BoolVar(&setupCmdFlag.doNotWriteProfile, "skip-profile", false, "do not write profile") } diff --git a/cmd/upgrade.go b/cmd/upgrade.go index bfa2565..f6e5952 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -11,6 +11,7 @@ import ( var upgradeCmdFlag struct { release string asset string + keep bool } // upgradeCmd represents the upgrade command @@ -26,6 +27,21 @@ var upgradeCmd = &cobra.Command{ core.NewCommandInstaller(), ) + if !upgradeCmdFlag.keep { + runner.Add( + core.NewContextValueSetter(map[define.ContextKey]interface{}{ + define.ContextKeyVersion: define.Version, + }), + core.NewCommandQuerierByNameAndVersion( + define.Name, define.Version, + ), + core.NewBinaryRemover(), + core.NewCommandRemover(), + core.NewBinaryRemover(), + core.NewCommandRemover(), + ) + } + utils.ExitWithError(runner.Run(utils.SetIntoContext(cmd.Context(), map[define.ContextKey]interface{}{ define.ContextKeyName: define.Name, define.ContextKeyCommandManaged: true, @@ -38,4 +54,5 @@ func init() { flags := upgradeCmd.Flags() flags.StringVarP(&upgradeCmdFlag.release, "release", "r", "latest", "cmdr release tag name") flags.StringVarP(&upgradeCmdFlag.asset, "asset", "a", define.Asset, "cmdr release assert name") + flags.BoolVarP(&upgradeCmdFlag.keep, "keep", "k", false, "keep the last cmdr version") } diff --git a/core/utils.go b/core/context.go similarity index 68% rename from core/utils.go rename to core/context.go index 1ed0f09..befab2b 100644 --- a/core/utils.go +++ b/core/context.go @@ -41,3 +41,22 @@ func GetCommandsFromContext(ctx context.Context) ([]*model.Command, error) { return commands, nil } + +type ContextValueSetter struct { + BaseStep + values map[define.ContextKey]interface{} +} + +func (s *ContextValueSetter) String() string { + return "context-value-setter" +} + +func (s *ContextValueSetter) Run(ctx context.Context) (context.Context, error) { + return utils.SetIntoContext(ctx, s.values), nil +} + +func NewContextValueSetter(values map[define.ContextKey]interface{}) *ContextValueSetter { + return &ContextValueSetter{ + values: values, + } +} diff --git a/core/shell.go b/core/shell.go new file mode 100644 index 0000000..16b2901 --- /dev/null +++ b/core/shell.go @@ -0,0 +1,89 @@ +package core + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/pkg/errors" + + "github.com/mrlyc/cmdr/define" + "github.com/mrlyc/cmdr/utils" +) + +type ShellProfiler struct { + BaseStep + shell string + script string +} + +func (s *ShellProfiler) String() string { + return "shell-profiler" +} + +func (s *ShellProfiler) isContainsProfile(path string) bool { + fs := define.FS + + file, err := fs.Open(path) + if err != nil { + return false + } + defer utils.CallClose(file) + + content, err := ioutil.ReadAll(file) + if err != nil { + return false + } + + return bytes.Contains(content, []byte(s.script)) +} + +func (s *ShellProfiler) Run(ctx context.Context) (context.Context, error) { + fs := define.FS + logger := define.Logger + homeDir, err := os.UserHomeDir() + if err != nil { + return ctx, errors.Wrapf(err, "failed to get user home dir") + } + + script := `eval "$(cmdr init)` + var profile string + switch s.shell { + case "bash": + profile = filepath.Join(homeDir, ".bashrc") + case "zsh": + profile = filepath.Join(homeDir, ".zshrc") + default: + logger.Warn("shell is not supported, please execute this script to init cmdr environment", map[string]interface{}{ + "shell": s.shell, + "script": script, + }) + } + + if s.isContainsProfile(profile) { + return ctx, nil + } + + file, err := fs.OpenFile(profile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return ctx, errors.Wrapf(err, "failed to open profile file") + } + defer utils.CallClose(file) + + _, err = fmt.Fprintf(file, "\n%s\n", script) + if err != nil { + return ctx, errors.Wrapf(err, "failed to write to profile file") + } + + return ctx, nil +} + +func NewShellProfiler(shell string) *ShellProfiler { + return &ShellProfiler{ + shell: filepath.Base(shell), + script: `eval "$(cmdr init)`, + } +}