Codebase list ruby-gitlab / upstream/4.1.0 lib / gitlab / cli_helpers.rb
upstream/4.1.0

Tree @upstream/4.1.0 (Download .tar.gz)

cli_helpers.rb @upstream/4.1.0raw · history · blame

require 'yaml'
require 'json'
require 'base64'

class Gitlab::CLI
  # Defines methods related to CLI output and formatting.
  module Helpers
    extend self

    # Returns actions available to CLI & Shell
    #
    # @return [Array]
    def actions
      @actions ||= Gitlab.actions
    end

    # Returns Gitlab::Client instance
    #
    # @return [Gitlab::Client]
    def client
      @client ||= Gitlab::Client.new(endpoint: (Gitlab.endpoint || ''))
    end

    # Returns method names and their owners
    #
    # @return [Array<Hash>]
    def method_owners
      @method_owners ||= actions.map do |action|
        {
          name: action.to_s,
          owner: client.method(action).owner.to_s
        }
      end
    end

    # Returns filtered required fields.
    #
    # @return [Array]
    def required_fields(args)
      if args.any? && args.last.is_a?(String) && args.last.start_with?('--only=')
        args.last.gsub('--only=', '').split(',')
      else
        []
      end
    end

    # Returns filtered excluded fields.
    #
    # @return [Array]
    def excluded_fields(args)
      if args.any? && args.last.is_a?(String) && args.last.start_with?('--except=')
        args.last.gsub('--except=', '').split(',')
      else
        []
      end
    end

    # Confirms command is valid.
    #
    # @return [Boolean]
    def valid_command?(cmd)
      command = cmd.is_a?(Symbol) ? cmd : cmd.to_sym
      Gitlab.actions.include?(command)
    end

    # Confirms command with a desctructive action.
    #
    # @return [String]
    def confirm_command(cmd)
      if cmd.start_with?('remove_') || cmd.start_with?('delete_')
        puts "Are you sure? (y/n)"
        if %w(y yes).include?($stdin.gets.to_s.strip.downcase)
          puts 'Proceeding..'
        else
          puts 'Command aborted.'
          exit(1)
        end
      end
    end

    # Gets defined help for a specific command/action.
    #
    # @return [String]
    def help(cmd=nil, &block)
      if cmd.nil? || Gitlab::Help.help_map.key?(cmd)
        Gitlab::Help.actions_table(cmd)
      else
        Gitlab::Help.get_help(cmd, &block)
      end
    end

    # Outputs a nicely formatted table or error msg.
    def output_table(cmd, args, data)
      case data
      when Gitlab::ObjectifiedHash, Gitlab::FileResponse
        puts record_table([data], cmd, args)
      when Gitlab::PaginatedResponse
        puts record_table(data, cmd, args)
      else # probably just an error msg
        puts data
      end
    end

    def output_json(cmd, args, data)
      if data.empty?
        puts '{}'
      else
        hash_result = case data
                      when Gitlab::ObjectifiedHash,Gitlab::FileResponse
                        record_hash([data], cmd, args, true)
                      when Gitlab::PaginatedResponse
                        record_hash(data, cmd, args)
                      else
                        { cmd: cmd, data: data, args: args }
        end
        puts JSON.pretty_generate(hash_result)
      end
    end

    # Table to display records.
    #
    # @return [Terminal::Table]
    def record_table(data, cmd, args)
      return 'No data' if data.empty?

      arr, keys = get_keys(args, data)

      table do |t|
        t.title = "Gitlab.#{cmd} #{args.join(', ')}"
        t.headings = keys

        arr.each_with_index do |hash, index|
          values = []

          keys.each do |key|
            case value = hash[key]
            when Hash
              value = value.has_key?('id') ? value['id'] : 'Hash'
            when StringIO
              value = 'File'
            when nil
              value = 'null'
            end

            values << value
          end

          t.add_row values
          t.add_separator unless arr.size - 1 == index
        end
      end
    end

    # Renders the result of given commands and arguments into a Hash
    #
    # @param  [Array]  data         Resultset from the API call
    # @param  [String] cmd          The command passed to the API
    # @param  [Array]  args         Options passed to the API call
    # @param  [bool]   single_value If set to true, a single result should be returned
    # @return [Hash]   Result hash
    def record_hash(data, cmd, args, single_value=false)
      if data.empty?
        result = nil
      else
        arr, keys = get_keys(args, data)
        result = []
        arr.each do |hash|
          row = {}

          keys.each do |key|
            case hash[key]
            when Hash
              row[key] = 'Hash'
            when StringIO
              row[key] = Base64.encode64(hash[key].read)
            when nil
              row[key] = nil
            else
              row[key] = hash[key]
            end
          end

          result.push row
        end
        result = result[0] if single_value && result.count > 0
      end

      {
        cmd: "Gitlab.#{cmd} #{args.join(', ')}".strip,
        result: result
      }
    end

    # Helper function to get rows and keys from data returned from API call
    def get_keys(args, data)
      arr = data.map(&:to_h)
      keys = arr.first.keys.sort { |x, y| x.to_s <=> y.to_s }
      keys &= required_fields(args) if required_fields(args).any?
      keys -= excluded_fields(args)
      [arr, keys]
    end

    # Helper function to call Gitlab commands with args.
    def gitlab_helper(cmd, args=[])
      begin
        data = args.any? ? Gitlab.send(cmd, *args) : Gitlab.send(cmd)
      rescue => e
        puts e.message
        yield if block_given?
      end

      data
    end

    # Convert a hash (recursively) to use symbol hash keys
    # @return [Hash]
    def symbolize_keys(hash)
      if hash.is_a?(Hash)
        hash = hash.each_with_object({}) do |(key, value), newhash|
          begin
            newhash[key.to_sym] = symbolize_keys(value)
          rescue NoMethodError
            raise "error: cannot convert hash key to symbol: #{key}"
          end
        end
      end

      hash
    end

    # YAML::load on a single argument
    def yaml_load(arg)
      begin
        yaml = YAML.load(arg)
      rescue Psych::SyntaxError
        raise "error: Argument is not valid YAML syntax: #{arg}"
      end
      yaml
    end
  end
end