fuelcell 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.codeclimate.yml +12 -0
- data/.gitignore +13 -0
- data/.rspec +5 -0
- data/.travis.yml +15 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +51 -0
- data/Rakefile +5 -0
- data/bin/console +9 -0
- data/bin/example.rb +9 -0
- data/bin/setup +7 -0
- data/bin/test +20 -0
- data/bin/world.rb +6 -0
- data/fuelcell.gemspec +26 -0
- data/lib/fuelcell/action/arg_definition.rb +36 -0
- data/lib/fuelcell/action/arg_results.rb +57 -0
- data/lib/fuelcell/action/args_manager.rb +66 -0
- data/lib/fuelcell/action/callable.rb +54 -0
- data/lib/fuelcell/action/command.rb +72 -0
- data/lib/fuelcell/action/not_found.rb +55 -0
- data/lib/fuelcell/action/opt_definition.rb +79 -0
- data/lib/fuelcell/action/opt_results.rb +68 -0
- data/lib/fuelcell/action/opts_manager.rb +80 -0
- data/lib/fuelcell/action/root.rb +76 -0
- data/lib/fuelcell/action/subcommands.rb +81 -0
- data/lib/fuelcell/action.rb +11 -0
- data/lib/fuelcell/cli.rb +89 -0
- data/lib/fuelcell/help/base_formatter.rb +24 -0
- data/lib/fuelcell/help/builder.rb +71 -0
- data/lib/fuelcell/help/cmds_formatter.rb +57 -0
- data/lib/fuelcell/help/desc_formatter.rb +21 -0
- data/lib/fuelcell/help/opts_formatter.rb +62 -0
- data/lib/fuelcell/help/usage_formatter.rb +88 -0
- data/lib/fuelcell/help.rb +27 -0
- data/lib/fuelcell/parser/arg_handler.rb +31 -0
- data/lib/fuelcell/parser/base_handler.rb +133 -0
- data/lib/fuelcell/parser/cmd_args_strategy.rb +28 -0
- data/lib/fuelcell/parser/ignore_handler.rb +25 -0
- data/lib/fuelcell/parser/opt_handler.rb +58 -0
- data/lib/fuelcell/parser/opt_name_handler.rb +89 -0
- data/lib/fuelcell/parser/opt_value_equal_handler.rb +26 -0
- data/lib/fuelcell/parser/parsing_strategy.rb +80 -0
- data/lib/fuelcell/parser/short_opt_no_space_handler.rb +54 -0
- data/lib/fuelcell/parser.rb +4 -0
- data/lib/fuelcell/shell.rb +102 -0
- data/lib/fuelcell/version.rb +3 -0
- data/lib/fuelcell.rb +114 -0
- metadata +148 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Action
|
3
|
+
class OptResults < ::Hash
|
4
|
+
|
5
|
+
def initialize(hash = {})
|
6
|
+
super()
|
7
|
+
hash.each do |key, value|
|
8
|
+
self[normalize(key)] = value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](key)
|
13
|
+
super(normalize(key))
|
14
|
+
end
|
15
|
+
|
16
|
+
def []=(key, value)
|
17
|
+
super(normalize(key), value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def delete(key)
|
21
|
+
super(normalize(key))
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch(key, *args)
|
25
|
+
super(normalize(key), *args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def key?(key)
|
29
|
+
super(normalize(key))
|
30
|
+
end
|
31
|
+
|
32
|
+
def values_at(*indices)
|
33
|
+
indices.map { |key| self[normalize(key)] }
|
34
|
+
end
|
35
|
+
|
36
|
+
def merge(hash)
|
37
|
+
dup.merge!(hash)
|
38
|
+
end
|
39
|
+
|
40
|
+
def merge!(hash)
|
41
|
+
hash.each do |key, value|
|
42
|
+
self[normalize(key)] = value
|
43
|
+
end
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_hash
|
48
|
+
Hash.new(default).merge!(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
def respond_to?(method_name, include_private = false)
|
52
|
+
return key?(method_name.to_s.chomp('?')) || super
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
def normalize(key)
|
58
|
+
key.is_a?(Symbol) ? key.to_s : key
|
59
|
+
end
|
60
|
+
|
61
|
+
def method_missing(method_name, *args, &_block)
|
62
|
+
name = method_name.to_s
|
63
|
+
return self.key?(name.chomp('?')) if name[-1] == '?'
|
64
|
+
return self[name]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Action
|
3
|
+
# Used by the Command to manage adding option from its dsl. It is also
|
4
|
+
# used during option parsing to find options, add global options, or check
|
5
|
+
# if any required options have been missed
|
6
|
+
class OptsManager
|
7
|
+
attr_reader :options
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@options = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def each
|
14
|
+
options.each {|_, option| yield option }
|
15
|
+
end
|
16
|
+
|
17
|
+
# Check to determine if any of the options are callable, meaning do
|
18
|
+
# they point to another command or have a lambda to be executed
|
19
|
+
#
|
20
|
+
# @return [Hash]
|
21
|
+
def callable
|
22
|
+
list = options.select { |_, opt| opt.callable? || opt.cmd_path? }
|
23
|
+
_, value = list.first
|
24
|
+
value
|
25
|
+
end
|
26
|
+
|
27
|
+
def required_opts
|
28
|
+
options.select { |_, opt| opt.required? }
|
29
|
+
end
|
30
|
+
|
31
|
+
def globals
|
32
|
+
options.select { |_, opt| opt.global? }
|
33
|
+
end
|
34
|
+
|
35
|
+
def missing_opts(names)
|
36
|
+
missing = required_opts.keys - names
|
37
|
+
return [] if missing.empty?
|
38
|
+
|
39
|
+
list = options.select { |(key, _)| missing.include?(key) }.values
|
40
|
+
yield list if block_given?
|
41
|
+
list
|
42
|
+
end
|
43
|
+
|
44
|
+
def add(option, config = {})
|
45
|
+
option = create(option, config) if option.is_a?(String)
|
46
|
+
if options.key?(option.name)
|
47
|
+
fail "can not add option: duplicate exists with name #{option.name}"
|
48
|
+
end
|
49
|
+
options[option.name] = option
|
50
|
+
end
|
51
|
+
alias_method :opt, :add
|
52
|
+
|
53
|
+
def remove(name)
|
54
|
+
# looks like an option definition so lets use it's name
|
55
|
+
if name.respond_to?(:name) && options.key?(name.name)
|
56
|
+
return options.delete(name.name)
|
57
|
+
end
|
58
|
+
|
59
|
+
opt = find(name)
|
60
|
+
return false unless opt
|
61
|
+
|
62
|
+
options.delete(opt.name)
|
63
|
+
end
|
64
|
+
|
65
|
+
def find(name)
|
66
|
+
target = false
|
67
|
+
options.each do |(_, option)|
|
68
|
+
target = option if option.name?(name)
|
69
|
+
end
|
70
|
+
target
|
71
|
+
end
|
72
|
+
alias_method :find_opt, :find
|
73
|
+
alias_method :[], :find
|
74
|
+
|
75
|
+
def create(name, config = {})
|
76
|
+
OptDefinition.new(name, config)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Action
|
3
|
+
# Top most command in the command hierarchy. The root command holds all
|
4
|
+
# other commands that would appear on the command line, it holds the
|
5
|
+
# name of the script that uses it.
|
6
|
+
class Root < Command
|
7
|
+
attr_reader :help
|
8
|
+
|
9
|
+
def initialize(name = nil)
|
10
|
+
name = script_name if name.nil?
|
11
|
+
super(name)
|
12
|
+
install_help
|
13
|
+
end
|
14
|
+
|
15
|
+
# Find any command in the root command.
|
16
|
+
#
|
17
|
+
# Using the cmd_args, which form a command hierarchy, we search for the
|
18
|
+
# deepest sub command first. If that it not found we assume that command
|
19
|
+
# arg is really a regular arg and we put it back. We do this until we
|
20
|
+
# reach the top command
|
21
|
+
#
|
22
|
+
# == Parameters:
|
23
|
+
# cmd_args <Array>:: A hierarchal list of commands to be searched
|
24
|
+
# remaining_args <Array>:: All remaining raw args from ARGV
|
25
|
+
#
|
26
|
+
# == Returns:
|
27
|
+
# <Fuelcell::Command> command object
|
28
|
+
def locate(cmd_args, raw_args = [])
|
29
|
+
return self if cmd_args.empty?
|
30
|
+
|
31
|
+
target = NotFound.new(cmd_args)
|
32
|
+
|
33
|
+
loop do
|
34
|
+
terms = cmd_args.dup
|
35
|
+
break if cmd_args.empty?
|
36
|
+
|
37
|
+
target = search(terms)
|
38
|
+
break unless target.is_a?(NotFound)
|
39
|
+
|
40
|
+
raw_args.unshift(cmd_args.pop)
|
41
|
+
end
|
42
|
+
|
43
|
+
# this must be an arg for the root command's action and not a command
|
44
|
+
return self if callable? && target.is_a?(NotFound)
|
45
|
+
|
46
|
+
target
|
47
|
+
end
|
48
|
+
|
49
|
+
def ensure_command_hierarchy(cmd_args)
|
50
|
+
create_tree(self, cmd_args)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def script_name
|
56
|
+
File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME))
|
57
|
+
end
|
58
|
+
|
59
|
+
def install_help
|
60
|
+
helper = callable_helper
|
61
|
+
command 'help' do
|
62
|
+
usage '[COMMAND]', 'describes subcommands or a specific command'
|
63
|
+
run helper
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def callable_helper
|
68
|
+
root = self
|
69
|
+
lambda do |_opts, args, shell|
|
70
|
+
text = Fuelcell::Help.generate(root, args, shell.terminal_width)
|
71
|
+
shell.puts text
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Action
|
3
|
+
module Subcommands
|
4
|
+
extend Forwardable
|
5
|
+
delegate [:empty?, :[], :each] => :subcommands
|
6
|
+
|
7
|
+
def add(cmds)
|
8
|
+
cmds = [cmds] unless cmds.is_a?(Array)
|
9
|
+
cmds.each { |cmd| subcommands[cmd.name] = cmd }
|
10
|
+
end
|
11
|
+
alias_method :<<, :add
|
12
|
+
|
13
|
+
def [](name)
|
14
|
+
list = name.split(' ')
|
15
|
+
deep_find(self, list)
|
16
|
+
end
|
17
|
+
|
18
|
+
def exist?(name)
|
19
|
+
list = name.split(' ')
|
20
|
+
result = deep_find(self, list)
|
21
|
+
result.is_a?(NotFound) ? false : true
|
22
|
+
end
|
23
|
+
|
24
|
+
# Finds a subcommand by name.
|
25
|
+
#
|
26
|
+
# @param names [Array] hierarchical order of commands
|
27
|
+
# @return [Fuelcell::Command] of nil when not found
|
28
|
+
def search(names)
|
29
|
+
deep_find(self, names)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Collect global options from the option manager of every
|
33
|
+
# command in the Hierarchy
|
34
|
+
#
|
35
|
+
# @return [Hash]
|
36
|
+
def global_options
|
37
|
+
collect_global_options(self, {})
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def subcommands
|
43
|
+
@subcommands ||= {}
|
44
|
+
end
|
45
|
+
|
46
|
+
def deep_find(cmd, names)
|
47
|
+
return cmd if names.empty?
|
48
|
+
|
49
|
+
name = names.shift
|
50
|
+
unless cmd.subcommands.key?(name)
|
51
|
+
names.unshift name
|
52
|
+
return NotFound.new(names)
|
53
|
+
end
|
54
|
+
deep_find(cmd.subcommands[name], names)
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_tree(current_cmd, tree)
|
58
|
+
while search_key = tree.shift
|
59
|
+
unless current_cmd.exist?(search_key)
|
60
|
+
current_cmd.command(search_key) {}
|
61
|
+
end
|
62
|
+
child = current_cmd[search_key]
|
63
|
+
create_tree(child, tree)
|
64
|
+
end
|
65
|
+
child
|
66
|
+
end
|
67
|
+
|
68
|
+
def collect_global_options(cmd, list)
|
69
|
+
globals = cmd.opts.globals
|
70
|
+
list.merge!(globals)
|
71
|
+
|
72
|
+
return list if cmd.empty?
|
73
|
+
|
74
|
+
cmd.each do |_, subcommand|
|
75
|
+
collect_global_options(subcommand, list)
|
76
|
+
end
|
77
|
+
list
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'fuelcell/action/callable'
|
2
|
+
require 'fuelcell/action/opt_definition'
|
3
|
+
require 'fuelcell/action/arg_definition'
|
4
|
+
require 'fuelcell/action/arg_results'
|
5
|
+
require 'fuelcell/action/opt_results'
|
6
|
+
require 'fuelcell/action/opts_manager'
|
7
|
+
require 'fuelcell/action/args_manager'
|
8
|
+
require 'fuelcell/action/subcommands'
|
9
|
+
require 'fuelcell/action/command'
|
10
|
+
require 'fuelcell/action/not_found'
|
11
|
+
require 'fuelcell/action/root'
|
data/lib/fuelcell/cli.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'fuelcell/shell'
|
2
|
+
require 'fuelcell/help'
|
3
|
+
require 'fuelcell/action'
|
4
|
+
require 'fuelcell/parser'
|
5
|
+
module Fuelcell
|
6
|
+
class Cli
|
7
|
+
attr_reader :root, :shell, :cmd_args_extractor, :parser
|
8
|
+
|
9
|
+
# Initializes with a root command object
|
10
|
+
#
|
11
|
+
# When nothing is given we default to the script name otherwise you choose
|
12
|
+
# the name of the root command or the command itself
|
13
|
+
#
|
14
|
+
def initialize(config = {})
|
15
|
+
@exit = config.fetch(:exit) { true }
|
16
|
+
@exit = @exit == false ? false : true
|
17
|
+
@root = config.fetch(:root) { Action::Root.new }
|
18
|
+
@shell = config.fetch(:shell) { Shell.new }
|
19
|
+
@parser = config.fetch(:parser) {
|
20
|
+
Parser::ParsingStrategy.new
|
21
|
+
}
|
22
|
+
|
23
|
+
@cmd_args_extractor = config.fetch(:cmd_args_extractor) {
|
24
|
+
Parser::CmdArgsStrategy.new
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Delegates all parsing responsiblities to a series of handlers, returning
|
29
|
+
# a structured hash needed to execute a command. The command being executed
|
30
|
+
# is determined by the CmdArgsStrategy unless you override it, it extracts
|
31
|
+
# all args upto the first option or ignore. The RootCommand is used to find
|
32
|
+
# the command using the extracted args, it accounts for sub commands. The
|
33
|
+
# parser Parser::ParsingStategy handles processing opts, args and ignored
|
34
|
+
# args
|
35
|
+
#
|
36
|
+
# @param raw_args [Array] cli args usually from ARGV
|
37
|
+
# @return [Hash] structured context for executing a command
|
38
|
+
def parse(raw_args)
|
39
|
+
cmd_args = cmd_args_extractor.call(raw_args)
|
40
|
+
cmd = root.locate(cmd_args, raw_args)
|
41
|
+
root.add_global_options(cmd)
|
42
|
+
begin
|
43
|
+
parser.call(cmd, raw_args)
|
44
|
+
rescue Exception => e
|
45
|
+
shell.error e.message
|
46
|
+
shell.failure_exit
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Executes the callable object in a command. All command callable object
|
51
|
+
# expect to be called the the options hash, arg hash and shell object.
|
52
|
+
#
|
53
|
+
# @param context [Hash]
|
54
|
+
# @return [Integer]
|
55
|
+
def execute(context)
|
56
|
+
cmd = context[:cmd]
|
57
|
+
opts = context[:opts] || {}
|
58
|
+
args = context[:args] || []
|
59
|
+
cli_shell = context[:shell] || shell
|
60
|
+
|
61
|
+
cmd = handle_callable_option(root, cmd)
|
62
|
+
|
63
|
+
cmd.call(opts, args, cli_shell)
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
def handle_callable_option(root, cmd)
|
68
|
+
opt_manager = cmd.opts
|
69
|
+
opt = opt_manager.callable
|
70
|
+
return cmd unless opt
|
71
|
+
return root.locate(opt.cmd_ath) if opt.cmd_path?
|
72
|
+
opt
|
73
|
+
end
|
74
|
+
|
75
|
+
def exit?
|
76
|
+
@exit
|
77
|
+
end
|
78
|
+
|
79
|
+
# Allows the system to by pass the exit call which is helpful in testing
|
80
|
+
# and when trying to manually control the system
|
81
|
+
#
|
82
|
+
# @param code [Int] integer from 0 .. 255 representing the exit code
|
83
|
+
# @return [Int] when exit is false
|
84
|
+
def handle_exit(code)
|
85
|
+
shell.exit code if exit?
|
86
|
+
code
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Help
|
3
|
+
# Hold common functionality and information needed by all formatters
|
4
|
+
class BaseFormatter
|
5
|
+
DEFAULT_WIDTH = 80
|
6
|
+
DEFAULT_PADDING = 4
|
7
|
+
attr_reader :width, :padding, :max_width
|
8
|
+
|
9
|
+
def initialize(config = {})
|
10
|
+
@width = config[:width] || DEFAULT_WIDTH
|
11
|
+
@padding = config[:padding] || DEFAULT_PADDING
|
12
|
+
@max_width = width - padding
|
13
|
+
end
|
14
|
+
|
15
|
+
def short_opt(data, no_opt = '')
|
16
|
+
data[:short].nil? ? no_opt : "-#{data[:short]}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def long_opt(data, no_opt = '')
|
20
|
+
data[:long].nil? ? no_opt : "--#{data[:long]}=#{data[:long].upcase}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Help
|
3
|
+
class Builder
|
4
|
+
|
5
|
+
def call(root, args, help = {})
|
6
|
+
cmd = args.empty? ? root : root.locate(args.raw)
|
7
|
+
path = args.join(' ').strip
|
8
|
+
build_usage(root.name, path, cmd, help)
|
9
|
+
build_options(cmd.opts, help)
|
10
|
+
build_commands(cmd, path, help)
|
11
|
+
build_desc(cmd, help)
|
12
|
+
help
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param script_name [String] name of script you asked help from
|
16
|
+
# @param path [String] path of command you need help for
|
17
|
+
# @param cmd [Fuelcell::Action::Command] command you need help with
|
18
|
+
# @param data [Hash] stores the structure of the help content
|
19
|
+
# @return [Hash]
|
20
|
+
def build_usage(script_name, path, cmd, data = {})
|
21
|
+
usage = {}
|
22
|
+
usage[:label] = 'Usage:'
|
23
|
+
usage[:path] = "#{script_name} #{path}".strip
|
24
|
+
usage[:text] = cmd.usage.nil? ? '' : cmd.usage
|
25
|
+
usage[:args] = []
|
26
|
+
usage[:opts] = []
|
27
|
+
|
28
|
+
cmd.opts.required_opts.each do |(_, opt)|
|
29
|
+
usage[:opts] << common_opt_data(opt)
|
30
|
+
end
|
31
|
+
|
32
|
+
data[:usage] = usage
|
33
|
+
data
|
34
|
+
end
|
35
|
+
|
36
|
+
def build_options(opt_manager, data = {})
|
37
|
+
options = []
|
38
|
+
opt_manager.each do |opt|
|
39
|
+
next if opt.required?
|
40
|
+
options << common_opt_data(opt).merge!(banner: opt.banner)
|
41
|
+
end
|
42
|
+
|
43
|
+
data[:options] = options
|
44
|
+
data
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_commands(cmd, path, data = {})
|
48
|
+
data[:commands] = []
|
49
|
+
cmd.each do |_, command|
|
50
|
+
data[:commands] << {
|
51
|
+
name: command.name,
|
52
|
+
path: path,
|
53
|
+
desc: command.desc || ''
|
54
|
+
}
|
55
|
+
end
|
56
|
+
data
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_desc(cmd, data = {})
|
60
|
+
data[:desc] = cmd.desc
|
61
|
+
data
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
def common_opt_data(opt)
|
67
|
+
{ short: opt.short, long: opt.long, type: opt.type }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Help
|
3
|
+
class CmdsFormatter < BaseFormatter
|
4
|
+
attr_reader :indent, :desc_space
|
5
|
+
def initialize(config = {})
|
6
|
+
super
|
7
|
+
@indent = (config[:indent] || 2).to_i
|
8
|
+
@desc_space = (config[:banner_space] || 2).to_i
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(data)
|
12
|
+
return '' if empty?(data)
|
13
|
+
str = "Commands:\n"
|
14
|
+
widest = widest_cmd(data[:commands])
|
15
|
+
data[:commands].each do |cmd|
|
16
|
+
str << line(cmd[:name], widest, cmd[:desc])
|
17
|
+
end
|
18
|
+
str
|
19
|
+
end
|
20
|
+
|
21
|
+
def line(cmd, widest_cmd, desc)
|
22
|
+
max = indent + desc_space + widest_cmd
|
23
|
+
desc = wrap(desc || '', max)
|
24
|
+
pad = create_padding(widest_cmd, cmd)
|
25
|
+
indent_str = ' ' * indent
|
26
|
+
column_space = ' ' * desc_space
|
27
|
+
"#{indent_str}#{cmd}#{pad}#{column_space}# #{desc}\n"
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_padding(widest, cmd)
|
31
|
+
' ' * (widest - cmd.size)
|
32
|
+
end
|
33
|
+
|
34
|
+
def empty?(data)
|
35
|
+
!data.key?(:commands) || data[:commands].empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
def wrap(text, widest_text)
|
39
|
+
return text if (widest_text + text.size) < max_width
|
40
|
+
|
41
|
+
pad = ' ' * widest_text
|
42
|
+
pattern = /(.{1,#{max_width - widest_text}})(\s+|$)/
|
43
|
+
text = text.gsub(pattern, "\\1\n#{pad}# ")
|
44
|
+
text.gsub(/# $/, '').strip
|
45
|
+
end
|
46
|
+
|
47
|
+
def widest_cmd(commands)
|
48
|
+
max = 0
|
49
|
+
commands.each do |data|
|
50
|
+
size = data[:name].size
|
51
|
+
max = size if size > max
|
52
|
+
end
|
53
|
+
max
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Help
|
3
|
+
class DescFormatter < BaseFormatter
|
4
|
+
|
5
|
+
def call(data)
|
6
|
+
return '' if empty?(data)
|
7
|
+
wrap(data[:long_desc] || data[:desc]) + "\n"
|
8
|
+
end
|
9
|
+
|
10
|
+
def wrap(text)
|
11
|
+
pattern = /(.{1,#{max_width}})(\s+|$)/
|
12
|
+
text.gsub(pattern, "\\1\n").strip
|
13
|
+
end
|
14
|
+
|
15
|
+
def empty?(data)
|
16
|
+
(!data.key?(:desc) || data[:desc].to_s.empty?) &&
|
17
|
+
(!data.key?(:long_desc) || data[:long_desc].to_s.empty?)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Help
|
3
|
+
class OptsFormatter < BaseFormatter
|
4
|
+
attr_reader :indent, :banner_space
|
5
|
+
def initialize(config = {})
|
6
|
+
super
|
7
|
+
@indent = (config[:indent] || 2).to_i
|
8
|
+
@banner_space = (config[:banner_space] || 2).to_i
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(data)
|
12
|
+
return '' if empty?(data)
|
13
|
+
|
14
|
+
str = "Options:\n"
|
15
|
+
widest = widest_opt(data[:options])
|
16
|
+
data[:options].each do |opt|
|
17
|
+
opt, banner = opt_data(opt)
|
18
|
+
str << line(opt, widest, banner)
|
19
|
+
end
|
20
|
+
str
|
21
|
+
end
|
22
|
+
|
23
|
+
def line(opt, widest_opt, banner_text)
|
24
|
+
max = indent + banner_space + widest_opt
|
25
|
+
banner = wrap(banner_text, max)
|
26
|
+
pad = create_padding(widest_opt, opt)
|
27
|
+
indent_str = ' ' * indent
|
28
|
+
column_space = ' ' * banner_space
|
29
|
+
"#{indent_str}#{opt}#{pad}#{column_space}#{banner}\n"
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_padding(widest, opt)
|
33
|
+
' ' * (widest - opt.size)
|
34
|
+
end
|
35
|
+
|
36
|
+
def empty?(data)
|
37
|
+
!data.key?(:options) || data[:options].empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
def wrap(text, widest_text)
|
41
|
+
return text if (widest_text + text.size) < max_width
|
42
|
+
|
43
|
+
pad = ' ' * widest_text
|
44
|
+
pattern = /(.{1,#{max_width - widest_text}})(\s+|$)/
|
45
|
+
text.gsub(pattern, "\\1\n#{pad}").strip
|
46
|
+
end
|
47
|
+
|
48
|
+
def widest_opt(options)
|
49
|
+
max = 0
|
50
|
+
options.each do |data|
|
51
|
+
size = "#{short_opt(data, ' ')} #{long_opt(data)}".size
|
52
|
+
max = size if size > max
|
53
|
+
end
|
54
|
+
max
|
55
|
+
end
|
56
|
+
|
57
|
+
def opt_data(data)
|
58
|
+
["#{short_opt(data, ' ')} #{long_opt(data)}", data[:banner] || '']
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|