fuelcell 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|