From b09d5af421a9cf9deeee5b66d00d3a66fc7f3c23 Mon Sep 17 00:00:00 2001 From: CHIKAMATSU Naohiro Date: Sun, 17 Apr 2022 00:54:54 +0900 Subject: [PATCH 1/6] Add process that generate bash/fish/zsh completion file --- cmd/root.go | 175 ++++++++++++++++++++++++++++++ internal/completion/completion.go | 28 +++++ 2 files changed, 203 insertions(+) create mode 100644 internal/completion/completion.go diff --git a/cmd/root.go b/cmd/root.go index d2bbf8a..2c89362 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,7 +1,16 @@ package cmd import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "github.com/nao1215/gup/internal/assets" + "github.com/nao1215/gup/internal/completion" + "github.com/nao1215/gup/internal/file" "github.com/nao1215/gup/internal/print" "github.com/spf13/cobra" ) @@ -15,7 +24,173 @@ If you update all binaries, just run '$ gup update'`, // Execute run gup process. func Execute() { assets.DeployIconIfNeeded() + deployShellCompletionFileIfNeeded(rootCmd) + + rootCmd.CompletionOptions.DisableDefaultCmd = true if err := rootCmd.Execute(); err != nil { print.Err(err) } } + +// deleteShellCompletionFileIfNeeded creates the shell completion file. +// If the file with the same contents already exists, it is not created. +func deployShellCompletionFileIfNeeded(cmd *cobra.Command) { + makeBashCompletionFileIfNeeded(cmd) + makeFishCompletionFileIfNeeded(cmd) + makeZshCompletionFileIfNeeded(cmd) +} + +func makeBashCompletionFileIfNeeded(cmd *cobra.Command) { + if existSameBashCompletionFile(cmd) { + return + } + + path := completion.BashCompletionFilePath() + if err := os.MkdirAll(filepath.Dir(path), 0775); err != nil { + print.Err(fmt.Errorf("can not create bash-completion file: %w", err)) + return + } + + if err := cmd.GenBashCompletionFile(path); err != nil { + print.Err(fmt.Errorf("can not create bash-completion file: %w", err)) + return + } + print.Info("create bash-completion file: " + path) +} + +func makeFishCompletionFileIfNeeded(cmd *cobra.Command) { + if isSameFishCompletionFile(cmd) { + return + } + + path := completion.FishCompletionFilePath() + if err := os.MkdirAll(filepath.Dir(path), 0775); err != nil { + print.Err(fmt.Errorf("can not create fish-completion file: %w", err)) + return + } + + if err := cmd.GenFishCompletionFile(path, false); err != nil { + print.Err(fmt.Errorf("can not create fish-completion file: %w", err)) + return + } + print.Info("create fish-completion file: " + path) +} + +func makeZshCompletionFileIfNeeded(cmd *cobra.Command) { + if isSameZshCompletionFile(cmd) { + return + } + + path := completion.ZshCompletionFilePath() + if err := os.MkdirAll(filepath.Dir(path), 0775); err != nil { + print.Err(fmt.Errorf("can not create zsh-completion file: %w", err)) + return + } + + if err := cmd.GenZshCompletionFile(path); err != nil { + print.Err(fmt.Errorf("can not create zsh-completion file: %w", err)) + return + } + print.Info("create zsh-completion file: " + path) + + const zshFpath = "fpath=(~/.zsh/completion $fpath)" + zshrcPath := completion.ZshrcPath() + if !file.IsFile(zshrcPath) { + fp, err := os.OpenFile(zshrcPath, os.O_RDWR|os.O_CREATE, 0664) + if err != nil { + print.Err(fmt.Errorf("can not add zsh $fpath in .zshrc: %w", err)) + return + } + defer fp.Close() + if _, err := fp.WriteString(zshFpath); err != nil { + print.Err(fmt.Errorf("can not add zsh $fpath in .zshrc: %w", err)) + return + } + } + + zshrc, err := ioutil.ReadFile(zshrcPath) + if err != nil { + print.Err(fmt.Errorf("can not read .zshrc: %w", err)) + return + } + + if strings.Contains(string(zshrc), zshFpath) { + return + } + + fp, err := os.OpenFile(zshrcPath, os.O_RDWR|os.O_APPEND, 0664) + if err != nil { + print.Err(fmt.Errorf("can not add zsh $fpath in .zshrc: %w", err)) + return + } + defer fp.Close() + + if _, err := fp.WriteString(zshFpath); err != nil { + print.Err(fmt.Errorf("can not add zsh $fpath in .zshrc: %w", err)) + return + } +} + +func existSameBashCompletionFile(cmd *cobra.Command) bool { + path := completion.BashCompletionFilePath() + if !file.IsFile(path) { + return false + } + + currentBashCompletion := new(bytes.Buffer) + cmd.GenBashCompletion(currentBashCompletion) + + bashCompletionInLocal, err := ioutil.ReadFile(path) + if err != nil { + return false + } + + if bytes.Compare(currentBashCompletion.Bytes(), bashCompletionInLocal) != 0 { + return false + } + return true +} + +func isSameFishCompletionFile(cmd *cobra.Command) bool { + path := completion.FishCompletionFilePath() + if !file.IsFile(path) { + return false + } + + currentFishCompletion := new(bytes.Buffer) + if err := cmd.GenFishCompletion(currentFishCompletion, false); err != nil { + return false + } + + fishCompletionInLocal, err := ioutil.ReadFile(path) + if err != nil { + return false + } + + if bytes.Compare(currentFishCompletion.Bytes(), fishCompletionInLocal) != 0 { + return false + } + return true +} + +func isSameZshCompletionFile(cmd *cobra.Command) bool { + path := completion.ZshCompletionFilePath() + if !file.IsFile(path) { + return false + } + + currentZshCompletion := new(bytes.Buffer) + if err := cmd.GenFishCompletion(currentZshCompletion, false); err != nil { + return false + } + + zshCompletionInLocal, err := ioutil.ReadFile(path) + if err != nil { + return false + } + + if bytes.Compare(currentZshCompletion.Bytes(), zshCompletionInLocal) != 0 { + return false + } + return true +} diff --git a/internal/completion/completion.go b/internal/completion/completion.go new file mode 100644 index 0000000..439c738 --- /dev/null +++ b/internal/completion/completion.go @@ -0,0 +1,28 @@ +package completion + +import ( + "os" + "path/filepath" + + "github.com/nao1215/gup/internal/cmdinfo" +) + +// BashCompletionFilePath return bash-completion file path. +func BashCompletionFilePath() string { + return filepath.Join(os.Getenv("HOME"), ".config", "bash_completion.d", cmdinfo.Name()) +} + +// FishCompletionFilePath return fish-completion file path. +func FishCompletionFilePath() string { + return filepath.Join(os.Getenv("HOME"), ".config", "fish", "completions", cmdinfo.Name()+".fish") +} + +// ZshCompletionFilePath return zsh-completion file path. +func ZshCompletionFilePath() string { + return filepath.Join(os.Getenv("HOME"), ".zsh", "completion", "_"+cmdinfo.Name()) +} + +// ZshrcPath return .zshrc path. +func ZshrcPath() string { + return filepath.Join(os.Getenv("HOME"), ".zshrc") +} From ec6196a90bf7a5e1bfb8cc52c43bf29bce30126a Mon Sep 17 00:00:00 2001 From: CHIKAMATSU Naohiro Date: Sun, 17 Apr 2022 01:40:32 +0900 Subject: [PATCH 2/6] Fix bug for zsh and fish --- cmd/root.go | 9 +++++++-- internal/completion/completion.go | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 2c89362..d303d5b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -93,7 +93,11 @@ func makeZshCompletionFileIfNeeded(cmd *cobra.Command) { } print.Info("create zsh-completion file: " + path) - const zshFpath = "fpath=(~/.zsh/completion $fpath)" + const zshFpath = ` +# setting for gup command (auto generate) +fpath=(~/.zsh/completion $fpath) +autoload -Uz compinit && compinit -i +` zshrcPath := completion.ZshrcPath() if !file.IsFile(zshrcPath) { fp, err := os.OpenFile(zshrcPath, os.O_RDWR|os.O_CREATE, 0664) @@ -102,6 +106,7 @@ func makeZshCompletionFileIfNeeded(cmd *cobra.Command) { return } defer fp.Close() + if _, err := fp.WriteString(zshFpath); err != nil { print.Err(fmt.Errorf("can not add zsh $fpath in .zshrc: %w", err)) return @@ -180,7 +185,7 @@ func isSameZshCompletionFile(cmd *cobra.Command) bool { } currentZshCompletion := new(bytes.Buffer) - if err := cmd.GenFishCompletion(currentZshCompletion, false); err != nil { + if err := cmd.GenZshCompletion(currentZshCompletion); err != nil { return false } diff --git a/internal/completion/completion.go b/internal/completion/completion.go index 439c738..bcee36b 100644 --- a/internal/completion/completion.go +++ b/internal/completion/completion.go @@ -9,7 +9,7 @@ import ( // BashCompletionFilePath return bash-completion file path. func BashCompletionFilePath() string { - return filepath.Join(os.Getenv("HOME"), ".config", "bash_completion.d", cmdinfo.Name()) + return filepath.Join(os.Getenv("HOME"), ".config", "bash_completion_", cmdinfo.Name()) } // FishCompletionFilePath return fish-completion file path. From bd78e28c69434f3eaea7b4ffc6205c449f77774b Mon Sep 17 00:00:00 2001 From: CHIKAMATSU Naohiro Date: Sun, 17 Apr 2022 02:10:21 +0900 Subject: [PATCH 3/6] Fix bug for bash completion --- cmd/root.go | 49 +++++++++++++++++++++++-------- internal/completion/completion.go | 2 +- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index d303d5b..382182a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -46,16 +46,37 @@ func makeBashCompletionFileIfNeeded(cmd *cobra.Command) { } path := completion.BashCompletionFilePath() - if err := os.MkdirAll(filepath.Dir(path), 0775); err != nil { - print.Err(fmt.Errorf("can not create bash-completion file: %w", err)) + bashCompletion := new(bytes.Buffer) + cmd.GenBashCompletion(bashCompletion) + + if !file.IsFile(path) { + fp, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0664) + if err != nil { + print.Err(fmt.Errorf("can not create .bash_completion: %w", err)) + return + } + defer fp.Close() + + if _, err := fp.WriteString(bashCompletion.String()); err != nil { + print.Err(fmt.Errorf("can not write .bash_completion %w", err)) + } + print.Info("create bash-completion file: " + path) return } - if err := cmd.GenBashCompletionFile(path); err != nil { - print.Err(fmt.Errorf("can not create bash-completion file: %w", err)) + fp, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND, 0664) + if err != nil { + print.Err(fmt.Errorf("can not append .bash_completion for gup: %w", err)) return } - print.Info("create bash-completion file: " + path) + defer fp.Close() + + if _, err := fp.WriteString(bashCompletion.String()); err != nil { + print.Err(fmt.Errorf("can not append .bash_completion for gup: %w", err)) + return + } + + print.Info("append bash-completion for gup: " + path) } func makeFishCompletionFileIfNeeded(cmd *cobra.Command) { @@ -109,8 +130,8 @@ autoload -Uz compinit && compinit -i if _, err := fp.WriteString(zshFpath); err != nil { print.Err(fmt.Errorf("can not add zsh $fpath in .zshrc: %w", err)) - return } + return } zshrc, err := ioutil.ReadFile(zshrcPath) @@ -137,20 +158,22 @@ autoload -Uz compinit && compinit -i } func existSameBashCompletionFile(cmd *cobra.Command) bool { - path := completion.BashCompletionFilePath() - if !file.IsFile(path) { + if !file.IsFile(completion.BashCompletionFilePath()) { return false } + return hasSameBashCompletionContent(cmd) +} - currentBashCompletion := new(bytes.Buffer) - cmd.GenBashCompletion(currentBashCompletion) - - bashCompletionInLocal, err := ioutil.ReadFile(path) +func hasSameBashCompletionContent(cmd *cobra.Command) bool { + bashCompletionFileInLocal, err := ioutil.ReadFile(completion.BashCompletionFilePath()) if err != nil { + print.Err(fmt.Errorf("can not read .bash_completion: %w", err)) return false } - if bytes.Compare(currentBashCompletion.Bytes(), bashCompletionInLocal) != 0 { + currentBashCompletion := new(bytes.Buffer) + cmd.GenBashCompletion(currentBashCompletion) + if !strings.Contains(string(bashCompletionFileInLocal), currentBashCompletion.String()) { return false } return true diff --git a/internal/completion/completion.go b/internal/completion/completion.go index bcee36b..c1cb62c 100644 --- a/internal/completion/completion.go +++ b/internal/completion/completion.go @@ -9,7 +9,7 @@ import ( // BashCompletionFilePath return bash-completion file path. func BashCompletionFilePath() string { - return filepath.Join(os.Getenv("HOME"), ".config", "bash_completion_", cmdinfo.Name()) + return filepath.Join(os.Getenv("HOME"), ".bash_completion") } // FishCompletionFilePath return fish-completion file path. From 0a062dd12ce2101fc4b7a0935579707aded6e9a2 Mon Sep 17 00:00:00 2001 From: CHIKAMATSU Naohiro Date: Sun, 17 Apr 2022 02:19:28 +0900 Subject: [PATCH 4/6] Update version to v0.10.0 --- Changelog.md | 3 +++ internal/cmdinfo/cmdinfo.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index a49f3b6..e8f7f60 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,9 @@ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +# [0.10.0] - 2022-04-17 +## Added +- Added automatic generation of completion files for bash, zsh, and fish # [0.9.3] - 2022-04-16 ## Changed - Parallelized update subcommand process diff --git a/internal/cmdinfo/cmdinfo.go b/internal/cmdinfo/cmdinfo.go index 1026289..0a92e74 100644 --- a/internal/cmdinfo/cmdinfo.go +++ b/internal/cmdinfo/cmdinfo.go @@ -6,7 +6,7 @@ import ( const ( name = "gup" - version = "0.9.4" + version = "0.10.0" ) // Version return gup command version. From c6ff23c11b3510a6e4c515f1fed88e32fab6fa51 Mon Sep 17 00:00:00 2001 From: CHIKAMATSU Naohiro Date: Sun, 17 Apr 2022 02:19:42 +0900 Subject: [PATCH 5/6] Add description for shell completion --- README.md | 10 ++++++++++ doc/ja/README.md | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/README.md b/README.md index b756780..cf70366 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,16 @@ $ ls /home/nao/.config/gup/gup.conf $ gup import ``` +### Auto-generate shell completion file (for bash, zsh, fish) +gup command automatically generates shell completion files for bash, zsh, and fish. After the user executes gup, if the shell completion file does not exist in the system, the auto-generation process will begin. To activate the completion feature, restart the shell. + +``` +$ gup +gup:INFO : create bash-completion file: /home/nao/.bash_completion +gup:INFO : create fish-completion file: /home/nao/.config/fish/completions/gup.fish +gup:INFO : create zsh-completion file: /home/nao/.zsh/completion/_gup +``` + # Contributing First off, thanks for taking the time to contribute! ❤️ See [CONTRIBUTING.md](./CONTRIBUTING.md) for more information. diff --git a/doc/ja/README.md b/doc/ja/README.md index bc3d43d..5cf103e 100644 --- a/doc/ja/README.md +++ b/doc/ja/README.md @@ -107,6 +107,15 @@ $ ls /home/nao/.config/gup/gup.conf /home/nao/.config/gup/gup.conf $ gup import ``` + +### シェル補完ファイルの自動生成 (bash, zsh, fish) +gupコマンドは、bash、zsh、fish向けのシェル補完ファイルを自動生成します。ユーザーがgupを実行した後、シェル補完ファイルがシステムに存在しない場合は、自動生成処理を開始します。シェル補完を有効にするには、シェルを再起動してください。 +``` +$ gup +gup:INFO : create bash-completion file: /home/nao/.bash_completion +gup:INFO : create fish-completion file: /home/nao/.config/fish/completions/gup.fish +gup:INFO : create zsh-completion file: /home/nao/.zsh/completion/_gup +``` # 連絡先 開発者に対して「バグ報告」や「機能の追加要望」がある場合は、コメントをください。その際、以下の連絡先を使用してください。 - [GitHub Issue](https://github.com/nao1215/gup/issues) From 77f319c5c90ca08f45c868aec76bc2dbef21d751 Mon Sep 17 00:00:00 2001 From: CHIKAMATSU Naohiro Date: Sun, 17 Apr 2022 02:22:46 +0900 Subject: [PATCH 6/6] Fix reviewdog --- cmd/root.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 382182a..72400a6 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -47,7 +47,10 @@ func makeBashCompletionFileIfNeeded(cmd *cobra.Command) { path := completion.BashCompletionFilePath() bashCompletion := new(bytes.Buffer) - cmd.GenBashCompletion(bashCompletion) + if err := cmd.GenBashCompletion(bashCompletion); err != nil { + print.Err(fmt.Errorf("can not generate bash completion content: %w", err)) + return + } if !file.IsFile(path) { fp, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0664) @@ -172,7 +175,9 @@ func hasSameBashCompletionContent(cmd *cobra.Command) bool { } currentBashCompletion := new(bytes.Buffer) - cmd.GenBashCompletion(currentBashCompletion) + if err := cmd.GenBashCompletion(currentBashCompletion); err != nil { + return false + } if !strings.Contains(string(bashCompletionFileInLocal), currentBashCompletion.String()) { return false }