cf-uaac 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/Gemfile +16 -0
- data/README.md +48 -0
- data/Rakefile +50 -0
- data/bin/completion-helper +80 -0
- data/bin/uaac +5 -0
- data/bin/uaac-completion.sh +34 -0
- data/bin/uaas +7 -0
- data/cf-uaac.gemspec +48 -0
- data/lib/cli.rb +15 -0
- data/lib/cli/base.rb +277 -0
- data/lib/cli/client_reg.rb +103 -0
- data/lib/cli/common.rb +187 -0
- data/lib/cli/config.rb +163 -0
- data/lib/cli/favicon.ico +0 -0
- data/lib/cli/group.rb +85 -0
- data/lib/cli/info.rb +54 -0
- data/lib/cli/runner.rb +52 -0
- data/lib/cli/token.rb +217 -0
- data/lib/cli/user.rb +108 -0
- data/lib/cli/version.rb +18 -0
- data/lib/stub/scim.rb +387 -0
- data/lib/stub/server.rb +310 -0
- data/lib/stub/uaa.rb +485 -0
- data/spec/client_reg_spec.rb +104 -0
- data/spec/common_spec.rb +89 -0
- data/spec/group_spec.rb +93 -0
- data/spec/http_spec.rb +165 -0
- data/spec/info_spec.rb +74 -0
- data/spec/spec_helper.rb +87 -0
- data/spec/token_spec.rb +119 -0
- data/spec/user_spec.rb +61 -0
- metadata +292 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Cloud Foundry 2012.02.03 Beta
|
2
|
+
# Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
5
|
+
# You may not use this product except in compliance with the License.
|
6
|
+
#
|
7
|
+
# This product includes a number of subcomponents with
|
8
|
+
# separate copyright notices and license terms. Your use of these
|
9
|
+
# subcomponents is subject to the terms and conditions of the
|
10
|
+
# subcomponent's license, as noted in the LICENSE file.
|
11
|
+
#
|
12
|
+
|
13
|
+
source "http://rubygems.org"
|
14
|
+
|
15
|
+
# Specify your gem's dependencies in uaa.gemspec
|
16
|
+
gemspec
|
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# CloudFoundry UAA Command Line Client
|
2
|
+
|
3
|
+
Command line gem for interacting with the CloudFoundry UAA server.
|
4
|
+
|
5
|
+
Set up a local ruby environment (so sudo not required):
|
6
|
+
|
7
|
+
`$ rvm use 1.9.2`
|
8
|
+
|
9
|
+
or
|
10
|
+
|
11
|
+
`$ rbenv global 1.9.2-p180`
|
12
|
+
|
13
|
+
see: https://rvm.io/ or http://rbenv.org/
|
14
|
+
|
15
|
+
Build the gem
|
16
|
+
|
17
|
+
`$ bundle install`
|
18
|
+
`$ gem build cf-uaac.gemspec`
|
19
|
+
|
20
|
+
Install it
|
21
|
+
|
22
|
+
`$ gem install cf-uaac*.gem`
|
23
|
+
|
24
|
+
Run it
|
25
|
+
|
26
|
+
`$ uaac help`
|
27
|
+
`$ uaac target uaa.cloudfoundry.com`
|
28
|
+
`$ uaac token get <your-cf-username>`
|
29
|
+
`$ uaac token decode`
|
30
|
+
|
31
|
+
To use the APIs, see: https://github.com/cloudfoundry/cf-uaa-lib
|
32
|
+
|
33
|
+
## Tests
|
34
|
+
|
35
|
+
Run the tests with rake:
|
36
|
+
|
37
|
+
`$ bundle exec rake test`
|
38
|
+
|
39
|
+
Run the tests and see a fancy coverage report:
|
40
|
+
|
41
|
+
`$ bundle exec rake cov`
|
42
|
+
|
43
|
+
Run integration tests (on a server running on localhost:8080/uaa):
|
44
|
+
|
45
|
+
`$ export UAA_CLIENT_ID="admin"`
|
46
|
+
`$ export UAA_CLIENT_SECRET="adminsecret"`
|
47
|
+
`$ export UAA_CLIENT_TARGET="http://localhost:8080/uaa"`
|
48
|
+
`$ bundle exec rspec spec/integration_spec.rb`
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Cloud Foundry 2012.02.03 Beta
|
2
|
+
# Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
5
|
+
# You may not use this product except in compliance with the License.
|
6
|
+
#
|
7
|
+
# This product includes a number of subcomponents with
|
8
|
+
# separate copyright notices and license terms. Your use of these
|
9
|
+
# subcomponents is subject to the terms and conditions of the
|
10
|
+
# subcomponent's license, as noted in the LICENSE file.
|
11
|
+
#
|
12
|
+
|
13
|
+
require "rdoc/task"
|
14
|
+
require "rspec/core/rake_task"
|
15
|
+
require "bundler/gem_tasks" # only available in bundler >= 1.0.15
|
16
|
+
require "ci/reporter/rake/rspec"
|
17
|
+
|
18
|
+
ENV['CI_REPORTS'] = File.expand_path("spec_reports")
|
19
|
+
COV_REPORTS = File.expand_path("coverage")
|
20
|
+
|
21
|
+
task :default => [:test]
|
22
|
+
task :tests => [:test]
|
23
|
+
task :spec => [:test]
|
24
|
+
|
25
|
+
RSpec::Core::RakeTask.new("test") do |t|
|
26
|
+
t.rspec_opts = ["--format", "documentation", "--colour"]
|
27
|
+
t.pattern = "spec/**/*_spec.rb"
|
28
|
+
end
|
29
|
+
|
30
|
+
RDoc::Task.new do |rd|
|
31
|
+
rd.rdoc_files.include("lib/**/*.rb")
|
32
|
+
rd.rdoc_dir = "doc"
|
33
|
+
end
|
34
|
+
|
35
|
+
task :ci => [:pre_coverage, :rcov_reports, "ci:setup:rspec", :test]
|
36
|
+
task :cov => [:pre_coverage, :test, :view_coverage]
|
37
|
+
task :coverage => [:pre_coverage, :test]
|
38
|
+
|
39
|
+
task :pre_coverage do
|
40
|
+
rm_rf COV_REPORTS
|
41
|
+
ENV['COVERAGE'] = "exclude-spec exclude-vendor"
|
42
|
+
end
|
43
|
+
|
44
|
+
task :rcov_reports do
|
45
|
+
ENV['COVERAGE'] += " rcov"
|
46
|
+
end
|
47
|
+
|
48
|
+
task :view_coverage do
|
49
|
+
`firefox #{File.join(COV_REPORTS, 'index.html')}`
|
50
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# given the indents, find sub-commands of a given command
|
4
|
+
def find_sub_commands (lines, indents, i)
|
5
|
+
result = Array.new
|
6
|
+
x = i+1
|
7
|
+
while x < lines.size && indents[x] > indents[i]
|
8
|
+
if indents[x] - indents[i] == 1
|
9
|
+
result.push(x)
|
10
|
+
end
|
11
|
+
x = x+1
|
12
|
+
end
|
13
|
+
result
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_sub_command_index (lines, sub_command_indices, sub_command)
|
17
|
+
sub_command_indices.each do |i|
|
18
|
+
if lines[i] == sub_command
|
19
|
+
return i
|
20
|
+
end
|
21
|
+
end
|
22
|
+
-1
|
23
|
+
end
|
24
|
+
|
25
|
+
# traverse sub-command tree with the given list of args
|
26
|
+
def traverse_command_tree(lines, sub_command_indices, args)
|
27
|
+
#puts "traversing for #{args}"
|
28
|
+
x = 0
|
29
|
+
args.drop(1).each do |arg|
|
30
|
+
next if arg.start_with? ("-")
|
31
|
+
nx = find_sub_command_index(lines, sub_command_indices[x], arg)
|
32
|
+
if nx != -1
|
33
|
+
x = nx
|
34
|
+
end
|
35
|
+
end
|
36
|
+
x
|
37
|
+
end
|
38
|
+
|
39
|
+
# use values in array1 as indices into array2 and find the subset from array2
|
40
|
+
def find_subset(values, indices)
|
41
|
+
result = Array.new
|
42
|
+
indices.each do |i|
|
43
|
+
result.push(values[i])
|
44
|
+
end
|
45
|
+
result.join(' ')
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_cache_filename(cmd)
|
49
|
+
curr_user = `whoami`.chomp
|
50
|
+
home_dir = `echo ~#{curr_user}`.chomp
|
51
|
+
"#{home_dir}/.#{cmd}-commands"
|
52
|
+
end
|
53
|
+
|
54
|
+
indent_char = "\t"
|
55
|
+
command_file_name = get_cache_filename(ARGV[0])
|
56
|
+
if !FileTest.exists?(command_file_name)
|
57
|
+
`#{ARGV[0]} help commands > #{command_file_name}`
|
58
|
+
end
|
59
|
+
lines = File.readlines(command_file_name)
|
60
|
+
children = Array.new
|
61
|
+
indents = Array.new
|
62
|
+
|
63
|
+
lines.each_with_index do |line, i|
|
64
|
+
indents[i] = line.count(indent_char)
|
65
|
+
end
|
66
|
+
|
67
|
+
# now that we have computed the indent level for each line, remove new-lines and tabs
|
68
|
+
lines.collect! { |line| line.gsub(/[#{indent_char}]/, '') }
|
69
|
+
lines.collect! { |line| line.gsub(/[\n]/, '') }
|
70
|
+
|
71
|
+
# pre-processing: find the sub-commands for command (using indent levels)
|
72
|
+
lines.each_with_index do |line, i|
|
73
|
+
children[i] = find_sub_commands(lines, indents, i)
|
74
|
+
end
|
75
|
+
|
76
|
+
# now that a sub-command tree is available, parse it using the input arguments to find the auto-completion options
|
77
|
+
x = traverse_command_tree(lines, children, ARGV.drop(1))
|
78
|
+
puts x == -1 ? "" : find_subset(lines, children[x])
|
79
|
+
|
80
|
+
|
data/bin/uaac
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#! /bin/bash
|
2
|
+
GLOBAL_OPTS="--help --no-help -h --version --no-version -v --debug --no-debug -d --trace --no-trace -t --config"
|
3
|
+
|
4
|
+
_debug() {
|
5
|
+
if [[ $UAAC_DEBUG -eq 1 ]] ; then
|
6
|
+
echo "$@;"
|
7
|
+
fi
|
8
|
+
}
|
9
|
+
|
10
|
+
_add_completion_options() {
|
11
|
+
local current="${COMP_WORDS[${COMP_CWORD}]}"
|
12
|
+
COMPREPLY=( "${COMPREPLY[@]}" $(compgen -W "$1" -- $current) )
|
13
|
+
}
|
14
|
+
|
15
|
+
_uaac() {
|
16
|
+
local current="${COMP_WORDS[${COMP_CWORD}]}"
|
17
|
+
local helper_input=()
|
18
|
+
if [[ "$current" == "" ]] || [[ "$current" == " " ]] || [[ $current == -* ]] ; then
|
19
|
+
helper_input=( ${COMP_WORDS[@]} )
|
20
|
+
else
|
21
|
+
helper_input=( ${COMP_WORDS[@]/$current/} )
|
22
|
+
fi
|
23
|
+
|
24
|
+
local parent_command="${COMP_WORDS[0]}"
|
25
|
+
local uaac_opts=$(completion-helper "${parent_command}" "${helper_input[@]}")
|
26
|
+
local opts=$uaac_opts
|
27
|
+
if [[ $current == -* ]] ; then
|
28
|
+
opts="${GLOBAL_OPTS} ${uaac_opts}"
|
29
|
+
fi
|
30
|
+
_add_completion_options "${opts}"
|
31
|
+
|
32
|
+
}
|
33
|
+
|
34
|
+
complete -F _uaac uaac
|
data/bin/uaas
ADDED
data/cf-uaac.gemspec
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Cloud Foundry 2012.02.03 Beta
|
4
|
+
# Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
|
5
|
+
#
|
6
|
+
# This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
7
|
+
# You may not use this product except in compliance with the License.
|
8
|
+
#
|
9
|
+
# This product includes a number of subcomponents with
|
10
|
+
# separate copyright notices and license terms. Your use of these
|
11
|
+
# subcomponents is subject to the terms and conditions of the
|
12
|
+
# subcomponent's license, as noted in the LICENSE file.
|
13
|
+
#
|
14
|
+
|
15
|
+
$:.push File.expand_path("../lib", __FILE__)
|
16
|
+
require "cli/version"
|
17
|
+
|
18
|
+
Gem::Specification.new do |s|
|
19
|
+
s.name = "cf-uaac"
|
20
|
+
s.version = CF::UAA::CLI_VERSION
|
21
|
+
s.authors = ["Dave Syer", "Dale Olds", "Joel D'sa", "Vidya Valmikinathan", "Luke Taylor"]
|
22
|
+
s.email = ["dsyer@vmware.com", "olds@vmware.com", "jdsa@vmware.com", "vidya@vmware.com", "ltaylor@vmware.com"]
|
23
|
+
s.homepage = "https://github.com/cloudfoundry/cf-uaac"
|
24
|
+
s.summary = %q{Command line interface for CloudFoundry UAA}
|
25
|
+
s.description = %q{Client command line tools for interacting with the CloudFoundry User Account and Authorization (UAA) server. The UAA is an OAuth2 Authorization Server so it can be used by webapps and command line apps to obtain access tokens to act on behalf of users. The tokens can then be used to access protected resources in a Resource Server. This library can be used by clients (as a convenient wrapper for mainstream oauth gems) or by resource servers.}
|
26
|
+
|
27
|
+
s.rubyforge_project = "cf-uaac"
|
28
|
+
|
29
|
+
s.files = `git ls-files`.split("\n")
|
30
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
31
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
32
|
+
s.require_paths = ["lib"]
|
33
|
+
|
34
|
+
# dependencies
|
35
|
+
s.add_development_dependency "bundler"
|
36
|
+
s.add_development_dependency "rake"
|
37
|
+
s.add_development_dependency "rspec"
|
38
|
+
s.add_development_dependency "simplecov"
|
39
|
+
s.add_development_dependency "simplecov-rcov"
|
40
|
+
s.add_development_dependency "ci_reporter"
|
41
|
+
s.add_runtime_dependency "highline"
|
42
|
+
s.add_runtime_dependency "cf-uaa-lib", ">= 1.3.0"
|
43
|
+
s.add_runtime_dependency "multi_json"
|
44
|
+
s.add_runtime_dependency "eventmachine"
|
45
|
+
s.add_runtime_dependency "launchy"
|
46
|
+
s.add_runtime_dependency "em-http-request", ">= 1.0.0.beta.3"
|
47
|
+
|
48
|
+
end
|
data/lib/cli.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#--
|
2
|
+
# Cloud Foundry 2012.02.03 Beta
|
3
|
+
# Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
|
4
|
+
#
|
5
|
+
# This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
6
|
+
# You may not use this product except in compliance with the License.
|
7
|
+
#
|
8
|
+
# This product includes a number of subcomponents with
|
9
|
+
# separate copyright notices and license terms. Your use of these
|
10
|
+
# subcomponents is subject to the terms and conditions of the
|
11
|
+
# subcomponent's license, as noted in the LICENSE file.
|
12
|
+
#++
|
13
|
+
|
14
|
+
require "cli/version"
|
15
|
+
require "cli/runner"
|
data/lib/cli/base.rb
ADDED
@@ -0,0 +1,277 @@
|
|
1
|
+
#--
|
2
|
+
# Cloud Foundry 2012.02.03 Beta
|
3
|
+
# Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
|
4
|
+
#
|
5
|
+
# This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
6
|
+
# You may not use this product except in compliance with the License.
|
7
|
+
#
|
8
|
+
# This product includes a number of subcomponents with
|
9
|
+
# separate copyright notices and license terms. Your use of these
|
10
|
+
# subcomponents is subject to the terms and conditions of the
|
11
|
+
# subcomponent's license, as noted in the LICENSE file.
|
12
|
+
#++
|
13
|
+
|
14
|
+
require 'highline'
|
15
|
+
require 'optparse'
|
16
|
+
|
17
|
+
module CF; module UAA end end
|
18
|
+
|
19
|
+
module CF::UAA
|
20
|
+
|
21
|
+
class Topic
|
22
|
+
|
23
|
+
class << self; attr_reader :synonyms end
|
24
|
+
|
25
|
+
def self.option_defs ; @option_defs || {} end
|
26
|
+
def self.commands; @commands || {} end
|
27
|
+
def self.topic(*args)
|
28
|
+
return @description if args.empty?
|
29
|
+
@synonyms = (args[0].split(' ') + args[1..-1]).map(&:downcase)
|
30
|
+
@description = args[0]
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.define_option(key, *args)
|
34
|
+
@option_defs ||= {}
|
35
|
+
raise "conflicting option definition for #{key}" if @option_defs.key?(key) && @option_defs[key] != args
|
36
|
+
@option_defs[key] = args
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.desc(template, desc, *options, &handler)
|
40
|
+
parts, argc = template.split(' '), 0
|
41
|
+
cmd = parts.each_with_object([]) { |p, o|
|
42
|
+
if p =~ /^\[/
|
43
|
+
argc = parts[-1] =~ /\.\.\.\]$/ ? -1 : parts.length - o.length
|
44
|
+
break o
|
45
|
+
end
|
46
|
+
o << p
|
47
|
+
}
|
48
|
+
cmd_key = cmd.join('_').to_sym
|
49
|
+
define_method(cmd_key, handler)
|
50
|
+
@commands ||= {}
|
51
|
+
@commands[cmd_key] = {parts: cmd, argc: argc, template: template, desc: desc, options: options}
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(cli_class, options = {}, input = $stdin, output = $stdout)
|
55
|
+
@cli_class, @options, @input, @output = cli_class, options, input, output
|
56
|
+
@highline = HighLine.new(input, output)
|
57
|
+
end
|
58
|
+
|
59
|
+
def ask(prompt); @highline.ask("#{prompt}: ") end
|
60
|
+
def ask_pwd(prompt); @highline.ask("#{prompt}: ") { |q| q.echo = '*' } end
|
61
|
+
def say(msg); @output.puts(msg); msg end
|
62
|
+
def gripe(msg); @output.puts(msg) end
|
63
|
+
def opts; @options end
|
64
|
+
|
65
|
+
def terminal_columns
|
66
|
+
return @terminal_columns ||= 0 if @terminal_columns || !@output.tty?
|
67
|
+
cols = HighLine::SystemExtensions.terminal_size.first rescue 0
|
68
|
+
@terminal_columns = !cols || cols < 40 ? 0 : cols
|
69
|
+
end
|
70
|
+
|
71
|
+
def help_col_start
|
72
|
+
return @help_col_start ||= 35 if @help_col_start || terminal_columns == 0 || terminal_columns > 80
|
73
|
+
@help_col_start = terminal_columns / 2
|
74
|
+
end
|
75
|
+
|
76
|
+
def pp(obj, indent = 0, wrap = terminal_columns, label = nil)
|
77
|
+
case obj
|
78
|
+
when Array
|
79
|
+
if obj.empty? || !obj[0].is_a?(Hash) && !obj[0].is_a?(Array)
|
80
|
+
say_definition(indent, ("#{label}: " if label), Util.strlist(obj), nil, wrap)
|
81
|
+
else
|
82
|
+
say_definition(indent, "#{label}: ", nil, nil, wrap) if label
|
83
|
+
obj.each {|o| pp o, indent, wrap, '-' }
|
84
|
+
end
|
85
|
+
when Hash
|
86
|
+
say_definition(indent, label, nil, nil, wrap) if label
|
87
|
+
obj.each {|k, v| pp v, indent + 2, wrap, k.to_s}
|
88
|
+
when nil
|
89
|
+
else say_definition(indent, ("#{label}: " if label), obj.to_s, nil, wrap)
|
90
|
+
end
|
91
|
+
obj
|
92
|
+
end
|
93
|
+
|
94
|
+
def say_definition(indent, term, text = nil, start = help_col_start, wrap = terminal_columns)
|
95
|
+
cur = indent + (term ? term.length : 0)
|
96
|
+
indent < 1 ? @output.printf("%s", term) : @output.printf("%*c%s", indent, ' ', term)
|
97
|
+
if start.nil?
|
98
|
+
start = 2 if (start = indent + 4) > wrap
|
99
|
+
else
|
100
|
+
start = 2 if start > wrap
|
101
|
+
if cur < start
|
102
|
+
@output.printf("%*c", start - cur, ' ')
|
103
|
+
elsif cur > start
|
104
|
+
@output.printf("\n%*c", start, ' ')
|
105
|
+
end
|
106
|
+
cur = start
|
107
|
+
end
|
108
|
+
return @output.printf("\n") unless text && !text.empty?
|
109
|
+
text = text.dup
|
110
|
+
text.each_line do |line|
|
111
|
+
width = wrap == 0 ? 4096 : wrap - cur
|
112
|
+
line = line.chomp
|
113
|
+
while line.length > width
|
114
|
+
i = line.rindex(' ', width) || width
|
115
|
+
@output.printf("%s\n%*c", line[0..i - 1], start, ' ')
|
116
|
+
width = wrap == 0 ? 4096 : wrap - start
|
117
|
+
line = line[i..-1].strip
|
118
|
+
end
|
119
|
+
@output.printf("%s\n", line)
|
120
|
+
cur = start
|
121
|
+
end
|
122
|
+
nil
|
123
|
+
end
|
124
|
+
|
125
|
+
def opt_help(key, args)
|
126
|
+
raise "missing option definition for #{key}" unless args
|
127
|
+
long = short = desc = nil
|
128
|
+
args.each do |a|
|
129
|
+
case a
|
130
|
+
when /^-.$/ then short = a
|
131
|
+
when /^--.*/ then long = a
|
132
|
+
else desc = a
|
133
|
+
end
|
134
|
+
end
|
135
|
+
raise "option definition must include long form (--#{key})" unless long
|
136
|
+
[ short ? "#{short} | #{long}" : "#{long}", desc]
|
137
|
+
end
|
138
|
+
|
139
|
+
def opt_strs(opts)
|
140
|
+
opts.each_with_object([]) { |o, a|
|
141
|
+
@cli_class.option_defs[o].each { |d|
|
142
|
+
case d
|
143
|
+
when /^--\[no-\](\S+)/ then a << "--#{$1} --no-#{$1}"
|
144
|
+
when /^--(\S+)/ then a << "--#{$1}"
|
145
|
+
end
|
146
|
+
}
|
147
|
+
}.join(' ')
|
148
|
+
end
|
149
|
+
|
150
|
+
def say_cmd_helper(info, suffix = nil)
|
151
|
+
say_definition 2, info[:template], info[:desc]
|
152
|
+
info[:options].each do |o|
|
153
|
+
odef, desc = opt_help(o, @cli_class.option_defs[o])
|
154
|
+
say_definition help_col_start, "", desc ? "#{odef}, #{desc}" : odef
|
155
|
+
end
|
156
|
+
@output.print suffix
|
157
|
+
end
|
158
|
+
|
159
|
+
def say_command_help(args)
|
160
|
+
say ""
|
161
|
+
@cli_class.topics.each do |tpc|
|
162
|
+
tpc.commands.each do |k, v|
|
163
|
+
if args[0..v[:parts].length - 1] == v[:parts]
|
164
|
+
say_cmd_helper(v, "\n")
|
165
|
+
return "help command"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
args = args.map(&:downcase)
|
170
|
+
@cli_class.topics.each { |tpc| return say_help(tpc) unless (args & tpc.synonyms).empty? }
|
171
|
+
gripe "No command or topic found to match: #{args.join(' ')}\n"
|
172
|
+
end
|
173
|
+
|
174
|
+
def say_help(topic = nil)
|
175
|
+
@output.print "\n#{@cli_class.overview}\n" unless topic
|
176
|
+
@cli_class.topics.each do |tpc|
|
177
|
+
next if topic && topic != tpc
|
178
|
+
@output.print "\n#{tpc.topic}\n"
|
179
|
+
tpc.commands.each { |k, v| say_cmd_helper v }
|
180
|
+
end
|
181
|
+
if topic || !@cli_class.global_options
|
182
|
+
@output.print("\n")
|
183
|
+
return topic ? "help topic" : "help"
|
184
|
+
end
|
185
|
+
@output.print "\nGlobal options:\n"
|
186
|
+
@cli_class.global_options.each do |o|
|
187
|
+
odef, desc = opt_help(o, @cli_class.option_defs[o])
|
188
|
+
say_definition 2, odef, desc
|
189
|
+
end
|
190
|
+
@output.print("\n")
|
191
|
+
"help"
|
192
|
+
end
|
193
|
+
|
194
|
+
def add_command(branches, parts, opts = nil)
|
195
|
+
if parts.empty?
|
196
|
+
return if opts.nil? || opts.empty?
|
197
|
+
return branches << {label: opt_strs(opts)}
|
198
|
+
end
|
199
|
+
if i = branches.find_index { |b| parts[0] == b[:label] }
|
200
|
+
parts.shift
|
201
|
+
else
|
202
|
+
branches << {label: parts.shift, sub: []}
|
203
|
+
i = -1
|
204
|
+
end
|
205
|
+
add_command(branches[i][:sub], parts, opts)
|
206
|
+
end
|
207
|
+
|
208
|
+
def print_tree(branches, indent)
|
209
|
+
return unless branches
|
210
|
+
branches.each do |b|
|
211
|
+
indent.times { @output.print "\t" };
|
212
|
+
@output.puts b[:label]
|
213
|
+
print_tree b[:sub], indent + 1
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def say_commands
|
218
|
+
tree = {label: File.basename($0), sub: []}
|
219
|
+
@cli_class.topics.each {|t| t.commands.each {|k, v| add_command(tree[:sub], v[:parts].dup, v[:options])}}
|
220
|
+
add_command(tree[:sub], [], @cli_class.global_options)
|
221
|
+
@output.puts tree[:label]
|
222
|
+
print_tree(tree[:sub], 1)
|
223
|
+
"help commands"
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
|
228
|
+
class BaseCli
|
229
|
+
|
230
|
+
class << self
|
231
|
+
attr_reader :input, :output, :option_defs
|
232
|
+
attr_accessor :overview, :topics, :global_options
|
233
|
+
end
|
234
|
+
|
235
|
+
def self.preprocess_options(args, opts); end # to be implemented in subclass
|
236
|
+
def self.too_many_args(cmd); end # to be implemented in subclass
|
237
|
+
|
238
|
+
def self.run(args = ARGV)
|
239
|
+
@input ||= $stdin
|
240
|
+
@output ||= $stdout
|
241
|
+
@option_defs = {}
|
242
|
+
@output.string = "" if @output.respond_to?(:string)
|
243
|
+
args = args.split if args.respond_to?(:split)
|
244
|
+
@parser = OptionParser.new
|
245
|
+
opts = @topics.each_with_object({}) do |tpc, o|
|
246
|
+
tpc.option_defs.each do |k, optdef|
|
247
|
+
@parser.on(*optdef) { |v| o[k] = v }
|
248
|
+
@option_defs[k] = optdef
|
249
|
+
end
|
250
|
+
end
|
251
|
+
@parser.parse! args
|
252
|
+
preprocess_options(args, opts)
|
253
|
+
@topics.each do |tpc|
|
254
|
+
tpc.commands.each do |k, v|
|
255
|
+
next unless args[0..v[:parts].length - 1] == v[:parts]
|
256
|
+
args = args[v[:parts].length..-1]
|
257
|
+
if v[:argc] == -1
|
258
|
+
# variable args, leave args alone
|
259
|
+
elsif args.length > v[:argc]
|
260
|
+
too_many_args(v[:parts].dup)
|
261
|
+
return nil
|
262
|
+
elsif args.length < v[:argc]
|
263
|
+
(v[:argc] - args.length).times { args << nil }
|
264
|
+
end
|
265
|
+
return tpc.new(self, opts, @input, @output).send(k, *args)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
@output.puts "#{File.basename($0)}: subcommand not found"
|
269
|
+
rescue Exception => e
|
270
|
+
@output.puts "#{File.basename($0)} error", "#{e.class}: #{e.message}", (e.backtrace if opts[:trace])
|
271
|
+
ensure
|
272
|
+
puts @output.string if opts[:trace] && @print_on_trace
|
273
|
+
end
|
274
|
+
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|