Skip to content

Commit

Permalink
Merge branch 'cli'
Browse files Browse the repository at this point in the history
  • Loading branch information
NARKOZ committed May 20, 2014
2 parents 23be13e + ac54e55 commit 6135248
Show file tree
Hide file tree
Showing 10 changed files with 307 additions and 10 deletions.
7 changes: 7 additions & 0 deletions bin/gitlab
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env ruby

$:.unshift File.expand_path('../../lib', __FILE__)

require 'gitlab/cli'

Gitlab::CLI.start(ARGV)
1 change: 1 addition & 0 deletions gitlab.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Gem::Specification.new do |gem|
gem.require_paths = ["lib"]

gem.add_runtime_dependency 'httparty'
gem.add_runtime_dependency 'terminal-table'

gem.add_development_dependency 'rake'
gem.add_development_dependency 'rspec'
Expand Down
8 changes: 8 additions & 0 deletions lib/gitlab.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,12 @@ def self.method_missing(method, *args, &block)
def self.respond_to?(method)
return client.respond_to?(method) || super
end

# Returns an unsorted array of available client methods.
#
# @return [Array<Symbol>]
def self.actions
hidden = /endpoint|private_token|user_agent|sudo|get|post|put|\Adelete\z|validate|set_request_defaults/
(Gitlab::Client.instance_methods - Object.methods).reject {|e| e[hidden]}
end
end
50 changes: 50 additions & 0 deletions lib/gitlab/cli.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
require 'gitlab'
require 'terminal-table/import'
require_relative 'cli_helpers'

class Gitlab::CLI
extend Helpers

def self.start(args)
command = args.shift.strip rescue 'help'
run(command, args)
end

def self.run(cmd, args=[])
case cmd
when 'help'
puts actions_table
when '-v', '--version'
puts "Gitlab Ruby Gem #{Gitlab::VERSION}"
else
unless Gitlab.actions.include?(cmd.to_sym)
puts "Unknown command. Run `gitlab help` for a list of available commands."
exit(1)
end

if args.any? && (args.last.start_with?('--only=') || args.last.start_with?('--except='))
command_args = args[0..-2]
else
command_args = args
end

confirm_command(cmd)

begin
data = args.any? ? Gitlab.send(cmd, *command_args) : Gitlab.send(cmd)
rescue => e
puts e.message
exit(1)
end

case data
when Gitlab::ObjectifiedHash
puts single_record_table(data, cmd, args)
when Array
puts multiple_record_table(data, cmd, args)
else
puts data.inspect
end
end
end
end
141 changes: 141 additions & 0 deletions lib/gitlab/cli_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
class Gitlab::CLI
# Defines methods related to CLI output and formatting.
module Helpers
extend self

# Returns filtered required fields.
#
# @return [Array]
def required_fields(args)
if args.any? && 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.start_with?('--except=')
args.last.gsub('--except=', '').split(',')
else
[]
end
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

# Table with available commands.
#
# @return [String]
def actions_table
client = Gitlab::Client.new(endpoint: '')
actions = Gitlab.actions
methods = []

actions.each do |action|
methods << {
name: action,
owner: client.method(action).owner.to_s.gsub('Gitlab::Client::', '')
}
end

owners = methods.map {|m| m[:owner]}.uniq.sort
methods_c = methods.group_by {|m| m[:owner]}
methods_c = methods_c.map {|_, v| [_, v.sort_by {|hv| hv[:name]}] }
methods_c = Hash[methods_c.sort_by(&:first).map {|k, v| [k, v]}]
max_column_length = methods_c.values.max_by(&:size).size

rows = max_column_length.times.map do |i|
methods_c.keys.map do |key|
methods_c[key][i] ? methods_c[key][i][:name] : ''
end
end

table do |t|
t.title = "Available commands (#{actions.size} total)"
t.headings = owners

rows.each do |row|
t.add_row row
end
end
end

# Table for a single record.
#
# @return [String]
def single_record_table(data, cmd, args)
hash = data.to_h
keys = hash.keys.sort {|x, y| x.to_s <=> y.to_s }
keys = keys & required_fields(args) if required_fields(args).any?
keys = keys - excluded_fields(args)

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

keys.each_with_index do |key, index|
case value = hash[key]
when Hash
value = 'Hash'
when nil
value = 'null'
end

t.add_row [key, value]
t.add_separator unless keys.size - 1 == index
end
end
end

# Table for multiple records.
#
# @return [String]
def multiple_record_table(data, cmd, args)
return 'No data' if data.empty?

arr = data.map(&:to_h)
keys = arr.first.keys.sort {|x, y| x.to_s <=> y.to_s }
keys = keys & required_fields(args) if required_fields(args).any?
keys = keys - excluded_fields(args)

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 = 'Hash'
when nil
value = 'null'
end

values << value
end

t.add_row values
t.add_separator unless arr.size - 1 == index
end
end
end
end
end
16 changes: 8 additions & 8 deletions lib/gitlab/client.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
module Gitlab
# Wrapper for the Gitlab REST API.
class Client < API
Dir[File.expand_path('../client/*.rb', __FILE__)].each{|f| require f}
Dir[File.expand_path('../client/*.rb', __FILE__)].each {|f| require f}

include SystemHooks
include Users
include Branches
include Groups
include Issues
include Notes
include MergeRequests
include Milestones
include Snippets
include Notes
include Projects
include Repositories
include Branches
include MergeRequests
include Groups
include Snippets
include SystemHooks
include Users
end
end
4 changes: 2 additions & 2 deletions lib/gitlab/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ def options

# Resets all configuration options to the defaults.
def reset
self.endpoint = nil
self.private_token = nil
self.endpoint = ENV['GITLAB_API_ENDPOINT']
self.private_token = ENV['GITLAB_API_PRIVATE_TOKEN']
self.sudo = nil
self.user_agent = DEFAULT_USER_AGENT
end
Expand Down
70 changes: 70 additions & 0 deletions spec/gitlab/cli_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
require 'spec_helper'

describe Gitlab::CLI do
describe ".run" do
context "when command is version" do
it "should show gem version" do
output = capture_output { Gitlab::CLI.run('-v') }
expect(output).to eq("Gitlab Ruby Gem #{Gitlab::VERSION}\n")
end
end

context "when command is help" do
it "should show available actions" do
output = capture_output { Gitlab::CLI.run('help') }
expect(output).to include('Available commands')
expect(output).to include('MergeRequests')
expect(output).to include('team_members')
end
end

context "when command is user" do
before do
stub_get("/user", "user")
@output = capture_output { Gitlab::CLI.run('user') }
end

it "should show executed command" do
expect(@output).to include('Gitlab.user')
end

it "should show user data" do
expect(@output).to include('name')
expect(@output).to include('John Smith')
end
end
end

describe ".start" do
context "when command with excluded fields" do
before do
stub_get("/user", "user")
args = ['user', '--except=id,email,name']
@output = capture_output { Gitlab::CLI.start(args) }
end

it "should show user data with excluded fields" do
expect(@output).to_not include('John Smith')
expect(@output).to include('bio')
expect(@output).to include('created_at')
end
end

context "when command with required fields" do
before do
stub_get("/user", "user")
args = ['user', '--only=id,email,name']
@output = capture_output { Gitlab::CLI.start(args) }
end

it "should show user data with required fields" do
expect(@output).to include('id')
expect(@output).to include('name')
expect(@output).to include('email')
expect(@output).to include('John Smith')
expect(@output).to_not include('bio')
expect(@output).to_not include('created_at')
end
end
end
end
9 changes: 9 additions & 0 deletions spec/gitlab_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@
end
end

describe ".actions" do
it "should return an array of client methods" do
actions = Gitlab.actions
expect(actions).to be_an Array
expect(actions.first).to be_a Symbol
expect(actions.sort.first).to match(/add_/)
end
end

describe ".endpoint=" do
it "should set endpoint" do
Gitlab.endpoint = 'https://api.example.com'
Expand Down
11 changes: 11 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@
require 'webmock/rspec'

require File.expand_path('../../lib/gitlab', __FILE__)
require File.expand_path('../../lib/gitlab/cli', __FILE__)

def capture_output
out = StringIO.new
$stdout = out
$stderr = out
yield
$stdout = STDOUT
$stderr = STDERR
out.string
end

def load_fixture(name)
File.new(File.dirname(__FILE__) + "/fixtures/#{name}.json")
Expand Down

0 comments on commit 6135248

Please sign in to comment.