diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 2e24947fb0..c6659308ef 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -1622,6 +1622,98 @@ class Formula end private :extract_macho_slice_from + # Generate shell completions for a formula for bash, zsh, and fish, using the formula's executable. + # + # @param commands [Pathname, String] the path to the executable and any passed subcommand(s) + # to use for generating the completion scripts. + # @param base_name [String] the base name of the generated completion script. Defaults to the formula name. + # @param shells [Array] the shells to generate completion scripts for. Defaults to `[:bash, :zsh, :fish]`. + # @param shell_parameter_format [String, Symbol] specify how `shells` should each be passed + # to the `executable`. Takes either a String representing a prefix, or one of [:flag, :arg, :none]. + # Defaults to plainly passing the shell. + # + # @example Using default values for optional arguments + # generate_completions_from_executable(bin/"foo", "completions") + # translates to + # + # (bash_completion/"foo").write Utils.safe_popen_read({ "SHELL" => "bash" }, bin/"foo", "completions", "bash") + # + # (zsh_completion/"_foo").write Utils.safe_popen_read({ "SHELL" => "zsh" }, bin/"foo", "completions", "zsh") + # + # (fish_completion/"foo.fish").write Utils.safe_popen_read({ "SHELL" => "fish" }, bin/"foo", "completions", "fish") + # + # @example Selecting shells and using a different base_name + # generate_completions_from_executable(bin/"foo", "completions", shells: [:bash, :zsh], base_name: "bar") + # translates to + # + # (bash_completion/"bar").write Utils.safe_popen_read({ "SHELL" => "bash" }, bin/"foo", "completions", "bash") + # + # (zsh_completion/"_bar").write Utils.safe_popen_read({ "SHELL" => "zsh" }, bin/"foo", "completions", "zsh") + # + # @example Using predefined shell_parameter_format :flag + # generate_completions_from_executable(bin/"foo", "completions", shell_parameter_format: :flag, shells: [:bash]) + # translates to + # + # (bash_completion/"foo").write Utils.safe_popen_read({ "SHELL" => "bash" }, bin/"foo", "completions", "--bash") + # + # @example Using predefined shell_parameter_format :arg + # generate_completions_from_executable(bin/"foo", "completions", shell_parameter_format: :arg, shells: [:bash]) + # translates to + # + # (bash_completion/"foo").write Utils.safe_popen_read({ "SHELL" => "bash" }, bin/"foo", + # "completions", "--shell=bash") + # + # @example Using predefined shell_parameter_format :none + # generate_completions_from_executable(bin/"foo", "completions", shell_parameter_format: :none, shells: [:bash]) + # translates to + # + # (bash_completion/"foo").write Utils.safe_popen_read({ "SHELL" => "bash" }, bin/"foo", "completions") + # + # @example Using custom shell_parameter_format + # generate_completions_from_executable(bin/"foo", "completions", shell_parameter_format: "--selected-shell=", + # shells: [:bash]) + # translates to + # + # (bash_completion/"foo").write Utils.safe_popen_read({ "SHELL" => "bash" }, bin/"foo", + # "completions", "--selected-shell=bash") + sig { + params(commands: T.any(Pathname, String), base_name: String, shells: T::Array[Symbol], + shell_parameter_format: T.nilable(T.any(Symbol, String))).void + } + def generate_completions_from_executable(*commands, + base_name: name, + shells: [:bash, :zsh, :fish], + shell_parameter_format: nil) + completion_script_path_map = { + bash: bash_completion/base_name, + zsh: zsh_completion/"_#{base_name}", + fish: fish_completion/"#{base_name}.fish", + } + + shells.each do |shell| + script_path = completion_script_path_map[shell] + shell_parameter = if shell_parameter_format.nil? + shell.to_s + elsif shell_parameter_format == :flag + "--#{shell}" + elsif shell_parameter_format == :arg + "--shell=#{shell}" + elsif shell_parameter_format == :none + "" + else + "#{shell_parameter_format}#{shell}" + end + + popen_read_args = %w[] + popen_read_args << commands + popen_read_args << shell_parameter + popen_read_args.flatten! + + script_path.dirname.mkpath + script_path.write Utils.safe_popen_read({ "SHELL" => shell.to_s }, *popen_read_args) + end + end + # an array of all core {Formula} names # @private def self.core_names diff --git a/Library/Homebrew/test/formula_spec.rb b/Library/Homebrew/test/formula_spec.rb index 8ad56f402d..bada984de5 100644 --- a/Library/Homebrew/test/formula_spec.rb +++ b/Library/Homebrew/test/formula_spec.rb @@ -1996,4 +1996,28 @@ describe Formula do }.to raise_error("ignore_missing_libraries is available on Linux only") end end + + describe "#generate_completions_from_executable" do + let(:f) do + Class.new(Testball) do + def install + bin.mkpath + (bin/"foo").write <<-EOF + echo completion + EOF + + FileUtils.chmod "+x", bin/"foo" + + generate_completions_from_executable(bin/"foo", "test") + end + end.new + end + + it "generates completion scripts" do + f.brew { f.install } + expect(f.bash_completion/"testball").to be_a_file + expect(f.zsh_completion/"_testball").to be_a_file + expect(f.fish_completion/"testball.fish").to be_a_file + end + end end