From adc020a0514aff535504d8edcbc39212d16b5d6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niko=20K=C3=B6ser?= Date: Mon, 26 Dec 2022 21:12:57 +0100 Subject: [PATCH] feat(cli): add pkg edit command --- internal/cli/proji/package/edit.go | 113 ++++++++++++++++++++++++++ internal/cli/proji/package/package.go | 1 + internal/config/config.go | 16 ++-- 3 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 internal/cli/proji/package/edit.go diff --git a/internal/cli/proji/package/edit.go b/internal/cli/proji/package/edit.go new file mode 100644 index 0000000..74d70fe --- /dev/null +++ b/internal/cli/proji/package/edit.go @@ -0,0 +1,113 @@ +package pkg + +import ( + "bufio" + "context" + "os" + "os/exec" + "runtime" + + "github.com/nikoksr/simplog" + + "github.com/cockroachdb/errors" + "github.com/spf13/cobra" + + "github.com/nikoksr/proji/internal/cli" +) + +func newEditCommand() *cobra.Command { + var fileType string + + cmd := &cobra.Command{ + Use: "edit [OPTIONS] LABEL", + Short: "Edit details about an installed package", + Args: cobra.ExactArgs(1), + DisableFlagsInUseLine: true, + + RunE: func(cmd *cobra.Command, args []string) error { + return editPackage(cmd.Context(), args[0], fileType) + }, + } + + cmd.Flags().StringVarP(&fileType, "type", "t", "toml", "File type in which to apply the editing (toml, json)") + + return cmd +} + +func newCommand(ctx context.Context, name string, args ...string) *exec.Cmd { + cmd := exec.CommandContext(ctx, name, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + return cmd +} + +func openFile(ctx context.Context, system, path string) error { + logger := simplog.FromContext(ctx) + + if path == "" { + return errors.New("path is empty") + } + + // TODO: More options for opening files; potentially use config file to set default editor/switch between $EDITOR; + // $VISUAL, commands like xdg-open or other application names + // Set command depending on system + switch system { + case "darwin", "freebsd", "linux", "netbsd", "openbsd": + editor := os.Getenv("EDITOR") + logger.Debugf("opening file %q with editor %q", path, editor) + + return newCommand(ctx, editor, path).Run() + case "windows": + logger.Debugf("opening file %q with start command", path) + + // return exec.Command("rundll32", "url.dll,FileProtocolHandler", path).Start() + return newCommand(ctx, "start", path).Run() + default: + return errors.Newf("unsupported OS %q", system) + } +} + +func editPackage(ctx context.Context, label, fileType string) error { + logger := simplog.FromContext(ctx) + + // Get package manager from session + logger.Debug("getting package manager from cli session") + pama := cli.SessionFromContext(ctx).PackageManager + if pama == nil { + return errors.New("no package manager available") + } + + // Load package that should be edited + logger.Debugf("load package %q", label) + pkg, err := pama.GetByLabel(ctx, label) + if err != nil { + return errors.Wrapf(err, "get package %q", label) + } + + // Export package to temp dir. Note: an empty destination path will cause the package to be exported to a temp dir + logger.Debug("exporting package to temp dir") + path, err := exportPackage(ctx, "", fileType, pkg.ToConfig()) + if err != nil { + return errors.Wrap(err, "export package") + } + + // Edit package; open file is OS dependent and will open the file in the default editor. + logger.Infof("Opening config file of package %q in default editor", label) + if err := openFile(ctx, runtime.GOOS, path); err != nil { + return errors.Wrap(err, "open file") + } + + // Wait for user to finish editing + logger.Info("Press ENTER to confirm your changes") + scanner := bufio.NewScanner(os.Stdin) + scanner.Scan() + + // When we are done editing, we can import the edited package + if err := replacePackage(ctx, label, path); err != nil { + return errors.Wrap(err, "replace package") + } + + return nil +} diff --git a/internal/cli/proji/package/package.go b/internal/cli/proji/package/package.go index 24a2d75..725222c 100644 --- a/internal/cli/proji/package/package.go +++ b/internal/cli/proji/package/package.go @@ -12,6 +12,7 @@ func NewCommand() *cobra.Command { cmd.AddCommand( newImportCommand(), + newEditCommand(), newExportCommand(), newListCommand(), newMimicCommand(), diff --git a/internal/config/config.go b/internal/config/config.go index 4de68e4..d1d814c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -139,7 +139,6 @@ func newProvider(path string) *viper.Viper { // Allow for cross-platform paths path = filepath.Clean(path) - path = filepath.FromSlash(path) dir := filepath.Dir(path) // Set default configuration @@ -237,14 +236,6 @@ func (conf *Config) readFlags(cmdFlags *pflag.FlagSet) error { func load(ctx context.Context, path string, flags *pflag.FlagSet) (conf *Config, err error) { logger := simplog.FromContext(ctx) - // Clean up path - path = filepath.Clean(path) - - path, err = filepath.Abs(path) - if err != nil { - return nil, errors.Wrap(err, "get absolute config path") - } - // If no explicit path is given, use default path if path == "" { path, err = defaultConfigPath() @@ -255,6 +246,13 @@ func load(ctx context.Context, path string, flags *pflag.FlagSet) (conf *Config, logger.Debugf("no explicit config path given, using default path: %q", path) } + // Clean up path + path = filepath.Clean(path) + path, err = filepath.Abs(path) + if err != nil { + return nil, errors.Wrap(err, "get absolute config path") + } + // Create default config logger.Debugf("creating config provider with path: %q", path) conf = &Config{