escort 0.0.1 → 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/.gitignore +1 -0
- data/.irbrc +1 -0
- data/.rspec +3 -0
- data/.rvmrc +22 -0
- data/README.md +31 -56
- data/TODO.md +152 -0
- data/escort.gemspec +6 -2
- data/examples/1_1_basic.rb +15 -0
- data/examples/1_2_basic_requires_arguments.rb +15 -0
- data/examples/2_2_command.rb +18 -0
- data/examples/2_2_command_requires_arguments.rb +20 -0
- data/examples/2_3_nested_commands.rb +26 -0
- data/examples/3_validations.rb +31 -0
- data/examples/4_1_config_file.rb +42 -0
- data/examples/argument_handling/basic.rb +12 -0
- data/examples/argument_handling/basic_command.rb +18 -0
- data/examples/argument_handling/no_arguments.rb +14 -0
- data/examples/argument_handling/no_arguments_command.rb +20 -0
- data/examples/basic/app.rb +16 -0
- data/examples/command_aliases/app.rb +31 -0
- data/examples/config_file/.apprc2 +16 -0
- data/examples/config_file/app.rb +78 -0
- data/examples/config_file/sub_commands.rb +35 -0
- data/examples/default_command/app.rb +20 -0
- data/examples/sub_commands/app.rb +18 -0
- data/examples/validation_basic/app.rb +31 -0
- data/lib/escort.rb +51 -4
- data/lib/escort/action_command/base.rb +79 -0
- data/lib/escort/action_command/escort_utility_command.rb +53 -0
- data/lib/escort/app.rb +89 -36
- data/lib/escort/arguments.rb +20 -0
- data/lib/escort/auto_options.rb +71 -0
- data/lib/escort/error/error.rb +50 -0
- data/lib/escort/formatter/borderless_table.rb +102 -0
- data/lib/escort/formatter/common.rb +58 -0
- data/lib/escort/formatter/default_help_formatter.rb +106 -0
- data/lib/escort/formatter/options.rb +13 -0
- data/lib/escort/formatter/string_splitter.rb +30 -0
- data/lib/escort/formatter/terminal.rb +22 -0
- data/lib/escort/formatter/terminal_formatter.rb +52 -0
- data/lib/escort/global_pre_parser.rb +43 -0
- data/lib/escort/logger.rb +75 -0
- data/lib/escort/option_parser.rb +145 -0
- data/lib/escort/setup/configuration/generator.rb +75 -0
- data/lib/escort/setup/configuration/instance.rb +33 -0
- data/lib/escort/setup/configuration/loader.rb +37 -0
- data/lib/escort/setup/configuration/locator/base.rb +19 -0
- data/lib/escort/setup/configuration/locator/descending_to_home.rb +23 -0
- data/lib/escort/setup/configuration/merge_tool.rb +38 -0
- data/lib/escort/setup/configuration/reader.rb +36 -0
- data/lib/escort/setup/configuration/writer.rb +44 -0
- data/lib/escort/setup/dsl/action.rb +17 -0
- data/lib/escort/setup/dsl/command.rb +74 -0
- data/lib/escort/setup/dsl/config_file.rb +13 -0
- data/lib/escort/setup/dsl/global.rb +84 -0
- data/lib/escort/setup/dsl/options.rb +25 -0
- data/lib/escort/setup/dsl/validations.rb +25 -0
- data/lib/escort/setup_accessor.rb +194 -0
- data/lib/escort/trollop.rb +15 -4
- data/lib/escort/utils.rb +21 -0
- data/lib/escort/validator.rb +42 -0
- data/lib/escort/version.rb +1 -1
- data/spec/helpers/execute_action_matcher.rb +21 -0
- data/spec/helpers/exit_with_code_matcher.rb +21 -0
- data/spec/helpers/give_option_to_action_with_value_matcher.rb +22 -0
- data/spec/integration/basic_options_spec.rb +53 -0
- data/spec/integration/basic_spec.rb +24 -0
- data/spec/lib/escort/formatter/string_splitter_spec.rb +38 -0
- data/spec/lib/escort/setup_accessor_spec.rb +42 -0
- data/spec/lib/escort/utils_spec.rb +30 -0
- data/spec/spec_helper.rb +22 -0
- metadata +128 -16
- data/lib/escort/command.rb +0 -23
- data/lib/escort/dsl.rb +0 -20
- data/lib/escort/global_dsl.rb +0 -11
@@ -0,0 +1,79 @@
|
|
1
|
+
module Escort
|
2
|
+
module ActionCommand
|
3
|
+
class Base
|
4
|
+
attr_reader :options, :arguments, :config
|
5
|
+
|
6
|
+
def initialize(options, arguments, config={})
|
7
|
+
@options = options
|
8
|
+
@arguments = arguments
|
9
|
+
@config = config
|
10
|
+
@command_context = nil
|
11
|
+
@command_options = nil
|
12
|
+
@parent_options = nil
|
13
|
+
@grandparent_options = nil
|
14
|
+
@global_options = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def command_context
|
20
|
+
return @command_context if @command_context
|
21
|
+
@command_context = []
|
22
|
+
current_command_hash = options[:global][:commands]
|
23
|
+
until current_command_hash.keys.empty?
|
24
|
+
key = current_command_hash.keys.first
|
25
|
+
@command_context << key
|
26
|
+
current_command_hash = current_command_hash[key][:commands]
|
27
|
+
end
|
28
|
+
@command_context
|
29
|
+
end
|
30
|
+
|
31
|
+
def command_options
|
32
|
+
@command_options ||= context_hash(command_context)[:options] || {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def global_options
|
36
|
+
@global_options ||= options[:global][:options] || {}
|
37
|
+
end
|
38
|
+
|
39
|
+
def parent_options
|
40
|
+
@parent_options ||= ensure_parent{ |parent_context| context_hash(parent_context)[:options] || {} }
|
41
|
+
end
|
42
|
+
|
43
|
+
def grandparent_options
|
44
|
+
@grandparent_options ||= ensure_grandparent{ |grandparent_context| context_hash(grandparent_context)[:options] || {} }
|
45
|
+
end
|
46
|
+
|
47
|
+
#generation_number 1 is parent, 2 is grandparent and so on
|
48
|
+
#default is 3 for great grandparent
|
49
|
+
def ancestor_options(generation_number = 3)
|
50
|
+
ensure_ancestor(generation_number){ |ancestor_context| context_hash(ancestor_context)[:options] || {} }
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def context_hash(context)
|
56
|
+
context_hash = options[:global]
|
57
|
+
context.each do |command_name|
|
58
|
+
context_hash = context_hash[:commands][command_name]
|
59
|
+
end
|
60
|
+
context_hash
|
61
|
+
end
|
62
|
+
|
63
|
+
def ensure_parent(&block)
|
64
|
+
ensure_ancestor(1, &block)
|
65
|
+
end
|
66
|
+
|
67
|
+
def ensure_grandparent(&block)
|
68
|
+
ensure_ancestor(2, &block)
|
69
|
+
end
|
70
|
+
|
71
|
+
def ensure_ancestor(generation_number, &block)
|
72
|
+
return {} if generation_number < 1
|
73
|
+
return {} unless command_context.size > generation_number
|
74
|
+
ancestor_context = command_context.dup.slice(0, command_context.size - generation_number)
|
75
|
+
block.call(ancestor_context)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Escort
|
2
|
+
module ActionCommand
|
3
|
+
class EscortUtilityCommand < Base
|
4
|
+
attr_reader :setup
|
5
|
+
|
6
|
+
def initialize(setup, options, arguments, config = {})
|
7
|
+
super(options, arguments, config)
|
8
|
+
@setup = setup
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
current_command_options = command_options
|
13
|
+
if current_command_options[:create_config_given]
|
14
|
+
create_config(current_command_options[:create_config])
|
15
|
+
elsif current_command_options[:create_default_config_given]
|
16
|
+
create_default_config
|
17
|
+
elsif current_command_options[:update_config_given]
|
18
|
+
update_config(current_command_options[:update_config])
|
19
|
+
elsif current_command_options[:update_default_config_given]
|
20
|
+
update_default_config
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def create_config(path)
|
27
|
+
config_path = absolute_path(path)
|
28
|
+
Escort::Setup::Configuration::Writer.new(config_path, Escort::Setup::Configuration::Generator.new(setup).default_data).write
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_default_config
|
32
|
+
Escort::Setup::Configuration::Writer.new(default_config_path, Escort::Setup::Configuration::Generator.new(setup).default_data).write
|
33
|
+
end
|
34
|
+
|
35
|
+
def update_config(path)
|
36
|
+
config_path = absolute_path(path)
|
37
|
+
Escort::Setup::Configuration::Writer.new(config_path, Escort::Setup::Configuration::Generator.new(setup).default_data).update
|
38
|
+
end
|
39
|
+
|
40
|
+
def update_default_config
|
41
|
+
Escort::Setup::Configuration::Writer.new(default_config_path, Escort::Setup::Configuration::Generator.new(setup).default_data).update
|
42
|
+
end
|
43
|
+
|
44
|
+
def absolute_path(path)
|
45
|
+
File.expand_path(path)
|
46
|
+
end
|
47
|
+
|
48
|
+
def default_config_path
|
49
|
+
Escort::Setup::Configuration::Loader.new(setup, nil).default_config_path
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/escort/app.rb
CHANGED
@@ -1,58 +1,111 @@
|
|
1
1
|
module Escort
|
2
2
|
class App
|
3
|
-
|
4
|
-
include GlobalDsl
|
5
|
-
|
3
|
+
#TODO ensure that every command must have an action
|
6
4
|
class << self
|
7
|
-
def create(
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
5
|
+
def create(option_string = '', &block)
|
6
|
+
cli_app_configuration = Escort::Setup::Dsl::Global.new(&block)
|
7
|
+
setup = Escort::SetupAccessor.new(cli_app_configuration)
|
8
|
+
app = self.new(option_string, setup)
|
9
|
+
app.execute
|
10
|
+
exit(0)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :setup, :option_string
|
15
|
+
|
16
|
+
def initialize(option_string, setup)
|
17
|
+
@setup = setup
|
18
|
+
@option_string = option_string
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute
|
22
|
+
begin
|
23
|
+
AutoOptions.augment(setup)
|
24
|
+
|
25
|
+
cli_options = current_cli_options(option_string)
|
26
|
+
|
27
|
+
auto_options = Escort::GlobalPreParser.new(setup).parse(cli_options.dup)
|
28
|
+
Escort::Logger.setup_error_logger(auto_options)
|
29
|
+
|
30
|
+
#now we can start doing error logging everything above here has to be rock solid
|
31
|
+
|
32
|
+
configuration = Escort::Setup::Configuration::Loader.new(setup, auto_options).configuration
|
33
|
+
|
34
|
+
invoked_options, arguments = Escort::OptionParser.new(configuration, setup).parse(cli_options)
|
35
|
+
context = context_from_options(invoked_options[:global])
|
36
|
+
action = setup.action_for(context)
|
37
|
+
actual_arguments = Escort::Arguments.read(arguments, setup.arguments_required_for(context))
|
38
|
+
rescue => e
|
39
|
+
handle_escort_error(e)
|
19
40
|
end
|
41
|
+
execute_action(action, invoked_options, actual_arguments, configuration.user)
|
20
42
|
end
|
21
43
|
|
22
|
-
|
44
|
+
private
|
23
45
|
|
24
|
-
def
|
25
|
-
|
46
|
+
def current_cli_options(option_string)
|
47
|
+
passed_in_options = Escort::Utils.tokenize_option_string(option_string)
|
48
|
+
passed_in_options.empty? ? ARGV.dup : passed_in_options
|
26
49
|
end
|
27
50
|
|
28
|
-
def arguments
|
29
|
-
|
51
|
+
def execute_action(action, options, arguments, user_config)
|
52
|
+
begin
|
53
|
+
action.call(options, arguments, user_config)
|
54
|
+
rescue => e
|
55
|
+
handle_action_error(e)
|
56
|
+
end
|
30
57
|
end
|
31
58
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
59
|
+
def context_from_options(options)
|
60
|
+
commands_in_order(options)
|
61
|
+
end
|
35
62
|
|
36
|
-
|
37
|
-
|
63
|
+
def commands_in_order(options, commands = [])
|
64
|
+
if options[:commands].keys.empty?
|
65
|
+
commands
|
66
|
+
else
|
67
|
+
command = options[:commands].keys.first
|
68
|
+
commands << command
|
69
|
+
commands_in_order(options[:commands][command], commands)
|
38
70
|
end
|
39
71
|
end
|
40
72
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
73
|
+
def handle_escort_error(e)
|
74
|
+
if e.kind_of?(Escort::UserError)
|
75
|
+
print_escort_error_message(e)
|
76
|
+
error_logger.debug{ "Escort app failed to execute successfully, due to user error" }
|
77
|
+
exit(Escort::USER_ERROR_EXIT_CODE)
|
78
|
+
elsif e.kind_of?(Escort::ClientError)
|
79
|
+
print_escort_error_message(e)
|
80
|
+
error_logger.debug{ "Escort app failed to execute successfully, due to client setup error" }
|
81
|
+
exit(Escort::CLIENT_ERROR_EXIT_CODE)
|
82
|
+
else
|
83
|
+
print_escort_error_message(e)
|
84
|
+
error_logger.debug{ "Escort app failed to execute successfully, due to internal error" }
|
85
|
+
exit(Escort::INTERNAL_ERROR_EXIT_CODE)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def handle_action_error(e)
|
90
|
+
if e.kind_of?(Escort::Error)
|
91
|
+
print_escort_error_message(e)
|
92
|
+
error_logger.debug{ "Escort app failed to execute successfully, due to internal error" }
|
93
|
+
exit(Escort::INTERNAL_ERROR_EXIT_CODE)
|
94
|
+
else
|
95
|
+
print_stacktrace(e)
|
96
|
+
error_logger.debug{ "Escort app failed to execute successfully, due to unknown error" }
|
97
|
+
exit(Escort::EXTERNAL_ERROR_EXIT_CODE)
|
98
|
+
end
|
48
99
|
end
|
49
100
|
|
50
|
-
def
|
51
|
-
|
101
|
+
def print_stacktrace(e)
|
102
|
+
error_logger.error{ e }
|
52
103
|
end
|
53
104
|
|
54
|
-
def
|
55
|
-
|
105
|
+
def print_escort_error_message(e)
|
106
|
+
print_stacktrace(e)
|
107
|
+
error_logger.warn{ "\n\n" }
|
108
|
+
error_logger.warn{ "An internal Escort error has occurred, you should probably report it by creating an issue on github!" }
|
56
109
|
end
|
57
110
|
end
|
58
111
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'readline'
|
2
|
+
|
3
|
+
module Escort
|
4
|
+
class Arguments
|
5
|
+
class << self
|
6
|
+
def read(arguments, requires_arguments=false)
|
7
|
+
if arguments.empty? && requires_arguments
|
8
|
+
while command = Readline.readline("> ", true)
|
9
|
+
arguments << command
|
10
|
+
end
|
11
|
+
arguments = arguments.compact.keep_if{|value| value.length > 0}
|
12
|
+
if arguments.empty?
|
13
|
+
raise Escort::UserError.new("You must provide some arguments to this script")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
arguments
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Escort
|
2
|
+
class AutoOptions
|
3
|
+
class << self
|
4
|
+
def augment(setup)
|
5
|
+
if setup.has_config_file?
|
6
|
+
setup.add_global_option :config, "Configuration file to use for this execution", :short => :none, :long => '--config', :type => :string
|
7
|
+
|
8
|
+
setup.add_global_command :escort, :description => "Auto created utility command", :aliases => [] do |command|
|
9
|
+
command.requires_arguments false
|
10
|
+
|
11
|
+
command.options do |opts|
|
12
|
+
opts.opt :create_config, "Create configuration file at specified location", :short => :none, :long => '--create-config', :type => :string
|
13
|
+
opts.opt :create_default_config, "Create a default configuration file", :short => :none, :long => '--create-default-config', :type => :boolean, :default => false
|
14
|
+
opts.opt :update_config, "Update configuration file at specified location", :short => :none, :long => '--update-config', :type => :string
|
15
|
+
opts.opt :update_default_config, "Update the default configuration file", :short => :none, :long => '--update-default-config', :type => :boolean, :default => false
|
16
|
+
end
|
17
|
+
|
18
|
+
command.conflicting_options :create_config, :create_default_config, :update_config, :update_default_config
|
19
|
+
|
20
|
+
command.action do |options, arguments|
|
21
|
+
ActionCommand::EscortUtilityCommand.new(setup, options, arguments).execute
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
setup.add_global_option :verbosity, "Verbosity level of output for current execution (e.g. INFO, DEBUG)", :short => :none, :long => '--verbosity', :type => :string, :default => "WARN"
|
26
|
+
|
27
|
+
setup.add_global_option :error_output_format, "The format to use when outputting errors (e.g. basic, advanced)", :short => :none, :long => '--error-output-format', :type => :string, :default => "basic"
|
28
|
+
#TODO validations for the output format and the verbosity
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :options
|
33
|
+
|
34
|
+
def initialize(options)
|
35
|
+
@options = options
|
36
|
+
end
|
37
|
+
|
38
|
+
def non_default_config_path
|
39
|
+
if options[:config_given] && File.exists?(config_path)
|
40
|
+
config_path
|
41
|
+
elsif !options[:config_given]
|
42
|
+
nil
|
43
|
+
else
|
44
|
+
error_logger.warn "The given config file '#{options[:config]}' does not exist, falling back to default"
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def verbosity
|
50
|
+
error_verbosity.upcase
|
51
|
+
end
|
52
|
+
|
53
|
+
def error_formatter
|
54
|
+
error_output_format.to_sym
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def config_path
|
60
|
+
File.expand_path(options[:config])
|
61
|
+
end
|
62
|
+
|
63
|
+
def error_output_format
|
64
|
+
options[:error_output_format]
|
65
|
+
end
|
66
|
+
|
67
|
+
def error_verbosity
|
68
|
+
options[:verbosity]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Escort
|
2
|
+
INTERNAL_ERROR_EXIT_CODE = 1
|
3
|
+
CLIENT_ERROR_EXIT_CODE = 2
|
4
|
+
USER_ERROR_EXIT_CODE = 3
|
5
|
+
EXTERNAL_ERROR_EXIT_CODE = 10
|
6
|
+
|
7
|
+
#module to tag all exceptions coming out of Escort with
|
8
|
+
module Error
|
9
|
+
end
|
10
|
+
|
11
|
+
#all our exceptions will supported nesting other exceptions
|
12
|
+
#also all our exception will be a kind_of? Escort::Error
|
13
|
+
class BaseError < StandardError
|
14
|
+
include Error
|
15
|
+
attr_reader :original
|
16
|
+
|
17
|
+
def initialize(msg, original=$!)
|
18
|
+
super(msg)
|
19
|
+
@original = original
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_backtrace(bt)
|
23
|
+
if original
|
24
|
+
original.backtrace.reverse.each do |line|
|
25
|
+
bt.last == line ? bt.pop : break
|
26
|
+
end
|
27
|
+
original_first = original.backtrace.shift
|
28
|
+
bt.concat ["#{original_first}: #{original.message}"]
|
29
|
+
bt.concat original.backtrace
|
30
|
+
end
|
31
|
+
super(bt)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
#user did something invalid
|
36
|
+
class UserError < BaseError
|
37
|
+
end
|
38
|
+
|
39
|
+
#for errors with escort itself
|
40
|
+
class InternalError < BaseError
|
41
|
+
end
|
42
|
+
|
43
|
+
#for errors with how escort is being used
|
44
|
+
class ClientError < BaseError
|
45
|
+
end
|
46
|
+
|
47
|
+
#a dependency is temporarily unavailable
|
48
|
+
class TransientError < BaseError
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Escort
|
2
|
+
module Formatter
|
3
|
+
class BorderlessTable
|
4
|
+
attr_reader :column_count, :formatter
|
5
|
+
attr_accessor :rows
|
6
|
+
|
7
|
+
def initialize(formatter, options = {})
|
8
|
+
@formatter = formatter
|
9
|
+
@column_count = options[:columns] || 3
|
10
|
+
@rows = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def row(*column_values)
|
14
|
+
rows << column_values.map(&:to_s)
|
15
|
+
#TODO raise error if column values size doesn't match columns
|
16
|
+
end
|
17
|
+
|
18
|
+
def output(&block)
|
19
|
+
block.call(self)
|
20
|
+
|
21
|
+
rows.each do |cells|
|
22
|
+
virtual_row = normalize_virtual_row(virtual_row_for(cells))
|
23
|
+
physical_row_count_for(virtual_row).times do |physical_count|
|
24
|
+
physical_row = format_physical_row_values(physical_row_for(virtual_row, physical_count))
|
25
|
+
formatter.put physical_row.join("").chomp, :newlines => 1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def format_physical_row_values(physical_row)
|
33
|
+
physical_row.each_with_index.map do |value, index|
|
34
|
+
cell_value(value, index)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def physical_row_for(virtual_row, index)
|
39
|
+
virtual_row.map {|physical| physical[index]}
|
40
|
+
end
|
41
|
+
|
42
|
+
def virtual_row_for(column_values)
|
43
|
+
virtual_row = []
|
44
|
+
column_values.each_with_index do |cell, index|
|
45
|
+
virtual_row << Escort::Formatter::StringSplitter.new(column_width(index) - 1).split(cell)
|
46
|
+
end
|
47
|
+
normalize_virtual_row(virtual_row)
|
48
|
+
end
|
49
|
+
|
50
|
+
def normalize_virtual_row(virtual_row)
|
51
|
+
virtual_row.map do |physical|
|
52
|
+
while physical.size < physical_row_count_for(virtual_row)
|
53
|
+
physical << ""
|
54
|
+
end
|
55
|
+
physical
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def physical_row_count_for(virtual_row)
|
60
|
+
virtual_row.map {|physical| physical.size}.max
|
61
|
+
end
|
62
|
+
|
63
|
+
def column_width(column_index)
|
64
|
+
#TODO raise error if index out of bounds
|
65
|
+
width = fair_column_width(column_index)
|
66
|
+
if column_index == column_count - 1
|
67
|
+
width = last_column_width
|
68
|
+
end
|
69
|
+
width
|
70
|
+
end
|
71
|
+
|
72
|
+
def fair_column_width(index)
|
73
|
+
#TODO raise error if index out of bounds
|
74
|
+
width = values_in_column(index).map(&:length).max
|
75
|
+
width = width + 1
|
76
|
+
width > max_column_width ? max_column_width : width
|
77
|
+
end
|
78
|
+
|
79
|
+
def last_column_width
|
80
|
+
full_fair_column_width = max_column_width * column_count
|
81
|
+
all_but_last_fair_column_width = 0
|
82
|
+
(column_count - 1).times do |index|
|
83
|
+
all_but_last_fair_column_width += fair_column_width(index)
|
84
|
+
end
|
85
|
+
full_fair_column_width - all_but_last_fair_column_width
|
86
|
+
end
|
87
|
+
|
88
|
+
def values_in_column(column_index)
|
89
|
+
#TODO raise error if index out of bounds
|
90
|
+
rows.map{|cells| cells[column_index]}
|
91
|
+
end
|
92
|
+
|
93
|
+
def max_column_width
|
94
|
+
(formatter.terminal_columns - 1 - formatter.current_indent_string.length)/column_count
|
95
|
+
end
|
96
|
+
|
97
|
+
def cell_value(value, column_index)
|
98
|
+
sprintf("%-#{column_width(column_index)}s", value.strip)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|