lib/cli/kit/system.rb in cli-kit-3.3.0 vs lib/cli/kit/system.rb in cli-kit-4.0.0

- old
+ new

@@ -4,11 +4,11 @@ require 'English' module CLI module Kit module System - SUDO_PROMPT = CLI::UI.fmt("{{info:(sudo)}} Password: ") + SUDO_PROMPT = CLI::UI.fmt('{{info:(sudo)}} Password: ') class << self # Ask for sudo access with a message explaning the need for it # Will make subsequent commands capable of running with sudo for a period of time # # #### Parameters @@ -17,11 +17,11 @@ # #### Usage # `ctx.sudo_reason("We need to do a thing")` # def sudo_reason(msg) # See if sudo has a cached password - `env SUDO_ASKPASS=/usr/bin/false sudo -A true` + %x(env SUDO_ASKPASS=/usr/bin/false sudo -A true) return if $CHILD_STATUS.success? CLI::UI.with_frame_color(:blue) do puts(CLI::UI.fmt("{{i}} #{msg}")) end end @@ -88,21 +88,33 @@ # def capture3(*a, sudo: false, env: ENV, **kwargs) delegate_open3(*a, sudo: sudo, env: env, method: :capture3, **kwargs) end + def popen2(*a, sudo: false, env: ENV, **kwargs, &block) + delegate_open3(*a, sudo: sudo, env: env, method: :popen2, **kwargs, &block) + end + + def popen2e(*a, sudo: false, env: ENV, **kwargs, &block) + delegate_open3(*a, sudo: sudo, env: env, method: :popen2e, **kwargs, &block) + end + + def popen3(*a, sudo: false, env: ENV, **kwargs, &block) + delegate_open3(*a, sudo: sudo, env: env, method: :popen3, **kwargs, &block) + end + # Execute a command in the user's environment # Outputs result of the command without capturing it # # #### Parameters # - `*a`: A splat of arguments evaluated as a command. (e.g. `'rm', folder` is equivalent to `rm #{folder}`) # - `sudo`: If truthy, run this command with sudo. If String, pass to `sudo_reason` # - `env`: process environment with which to execute this command # - `**kwargs`: additional keyword arguments to pass to Process.spawn # # #### Returns - # - `status`: boolean success status of the command execution + # - `status`: The `Process:Status` result for the command execution # # #### Usage # `stat = CLI::Kit::System.system('ls', 'a_folder')` # def system(*a, sudo: false, env: ENV, **kwargs) @@ -114,31 +126,33 @@ pid = Process.spawn(env, *resolve_path(a, env), 0 => in_stream, :out => out_w, :err => err_w, **kwargs) out_w.close err_w.close handlers = if block_given? - { out_r => ->(data) { yield(data.force_encoding(Encoding::UTF_8), '') }, - err_r => ->(data) { yield('', data.force_encoding(Encoding::UTF_8)) } } + { + out_r => ->(data) { yield(data.force_encoding(Encoding::UTF_8), '') }, + err_r => ->(data) { yield('', data.force_encoding(Encoding::UTF_8)) }, + } else - { out_r => ->(data) { STDOUT.write(data) }, - err_r => ->(data) { STDOUT.write(data) } } + { + out_r => ->(data) { STDOUT.write(data) }, + err_r => ->(data) { STDOUT.write(data) }, + } end previous_trailing = Hash.new('') loop do ios = [err_r, out_r].reject(&:closed?) break if ios.empty? readers, = IO.select(ios) readers.each do |io| - begin - data, trailing = split_partial_characters(io.readpartial(4096)) - handlers[io].call(previous_trailing[io] + data) - previous_trailing[io] = trailing - rescue IOError - io.close - end + data, trailing = split_partial_characters(io.readpartial(4096)) + handlers[io].call(previous_trailing[io] + data) + previous_trailing[io] = trailing + rescue IOError + io.close end end Process.wait(pid) $CHILD_STATUS @@ -161,23 +175,31 @@ partial_character_index = min_bound + partial_character_sub_index [data.byteslice(0...partial_character_index), data.byteslice(partial_character_index..-1)] end + def os + return :mac if /darwin/.match(RUBY_PLATFORM) + return :linux if /linux/.match(RUBY_PLATFORM) + return :windows if /mingw32/.match(RUBY_PLATFORM) + + raise "Could not determine OS from platform #{RUBY_PLATFORM}" + end + private def apply_sudo(*a, sudo) a.unshift('sudo', '-S', '-p', SUDO_PROMPT, '--') if sudo sudo_reason(sudo) if sudo.is_a?(String) a end - def delegate_open3(*a, sudo: raise, env: raise, method: raise, **kwargs) + def delegate_open3(*a, sudo: raise, env: raise, method: raise, **kwargs, &block) a = apply_sudo(*a, sudo) - Open3.send(method, env, *resolve_path(a, env), **kwargs) + Open3.send(method, env, *resolve_path(a, env), **kwargs, &block) rescue Errno::EINTR - raise(Errno::EINTR, "command interrupted: #{a.join(' ')}") + raise(Errno::EINTR, "command interrupted: #{a.join(" ")}") end # Ruby resolves the program to execute using its own PATH, but we want it to # use the provided one, so we ensure ruby chooses to spawn a shell, which will # parse our command and properly spawn our target using the provided environment. @@ -187,20 +209,33 @@ # project. # # See https://github.com/Shopify/dev/pull/625 for more details. def resolve_path(a, env) # If only one argument was provided, make sure it's interpreted by a shell. - return ["true ; " + a[0]] if a.size == 1 + if a.size == 1 + if os == :windows + return ['break && ' + a[0]] + else + return ['true ; ' + a[0]] + end + end return a if a.first.include?('/') - paths = env.fetch('PATH', '').split(':') - item = paths.detect do |f| - command_path = "#{f}/#{a.first}" - File.executable?(command_path) && File.file?(command_path) + item = which(a.first, env) + a[0] = item if item + a + end + + def which(cmd, env) + exts = os == :windows ? env.fetch('PATHEXT').split(';') : [''] + env.fetch('PATH', '').split(File::PATH_SEPARATOR).each do |path| + exts.each do |ext| + exe = File.join(path, "#{cmd}#{ext}") + return exe if File.executable?(exe) && !File.directory?(exe) + end end - a[0] = "#{item}/#{a.first}" if item - a + nil end end end end end