escort 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.irbrc +2 -0
- data/.travis.yml +1 -1
- data/README.md +272 -3
- data/TODO.md +118 -71
- data/examples/.my_apprc +24 -0
- data/examples/basic_config_file +16 -0
- data/examples/basic_conflicts +1 -1
- data/examples/basic_with_everything +30 -0
- data/examples/suite_complex +65 -0
- data/examples/{command → suite_simple} +0 -0
- data/examples/suite_with_sub_commands +94 -0
- data/lib/escort.rb +6 -4
- data/lib/escort/action_command/base.rb +7 -5
- data/lib/escort/app.rb +2 -8
- data/lib/escort/auto_options.rb +5 -3
- data/lib/escort/formatter/cursor_position.rb +29 -0
- data/lib/escort/formatter/default_help_formatter.rb +37 -32
- data/lib/escort/formatter/option.rb +1 -1
- data/lib/escort/formatter/stream_output_formatter.rb +88 -0
- data/lib/escort/formatter/{borderless_table.rb → string_grid.rb} +21 -19
- data/lib/escort/formatter/string_splitter.rb +24 -4
- data/lib/escort/setup/configuration/loader.rb +8 -2
- data/lib/escort/setup/configuration/locator/chaining.rb +29 -0
- data/lib/escort/setup/configuration/locator/executing_script_directory.rb +15 -0
- data/lib/escort/setup/configuration/locator/specified_directory.rb +21 -0
- data/lib/escort/setup/configuration/reader.rb +4 -2
- data/lib/escort/setup/configuration/writer.rb +6 -2
- data/lib/escort/setup/dsl/command.rb +7 -8
- data/lib/escort/setup/dsl/global.rb +3 -51
- data/lib/escort/version.rb +1 -1
- data/spec/integration/basic_config_file_spec.rb +82 -0
- data/spec/integration/suite_simple_spec.rb +45 -0
- data/spec/integration/suite_sub_command_spec.rb +51 -0
- data/spec/lib/escort/action_command/base_spec.rb +200 -0
- data/spec/lib/escort/formatter/option_spec.rb +2 -2
- data/spec/lib/escort/formatter/stream_output_formatter_spec.rb +214 -0
- data/spec/lib/escort/formatter/string_grid_spec.rb +59 -0
- data/spec/lib/escort/setup/configuration/generator_spec.rb +101 -0
- data/spec/lib/escort/setup/configuration/loader_spec.rb +79 -0
- data/spec/lib/escort/setup/configuration/locator/chaining_spec.rb +81 -0
- data/spec/lib/escort/setup/configuration/locator/descending_to_home_spec.rb +57 -0
- data/spec/lib/escort/setup/configuration/locator/executing_script_directory_spec.rb +29 -0
- data/spec/lib/escort/setup/configuration/locator/specified_directory_spec.rb +33 -0
- data/spec/lib/escort/setup/configuration/merge_tool_spec.rb +41 -0
- data/spec/lib/escort/setup/configuration/reader_spec.rb +41 -0
- data/spec/lib/escort/setup/configuration/writer_spec.rb +75 -0
- data/spec/spec_helper.rb +2 -1
- metadata +44 -24
- data/examples/attic/1_1_basic.rb +0 -15
- data/examples/attic/1_2_basic_requires_arguments.rb +0 -15
- data/examples/attic/2_2_command.rb +0 -18
- data/examples/attic/2_2_command_requires_arguments.rb +0 -20
- data/examples/attic/2_3_nested_commands.rb +0 -26
- data/examples/attic/3_validations.rb +0 -31
- data/examples/attic/4_1_config_file.rb +0 -42
- data/examples/attic/argument_handling/basic.rb +0 -12
- data/examples/attic/argument_handling/basic_command.rb +0 -18
- data/examples/attic/argument_handling/no_arguments.rb +0 -14
- data/examples/attic/argument_handling/no_arguments_command.rb +0 -20
- data/examples/attic/command_aliases/app.rb +0 -31
- data/examples/attic/config_file/.apprc2 +0 -16
- data/examples/attic/config_file/app.rb +0 -78
- data/examples/attic/config_file/sub_commands.rb +0 -35
- data/examples/attic/default_command/app.rb +0 -20
- data/examples/attic/sub_commands/app.rb +0 -18
- data/examples/attic/validation_basic/app.rb +0 -31
- data/lib/escort/formatter/terminal_formatter.rb +0 -58
- data/lib/escort/setup/dsl/validations.rb +0 -25
@@ -0,0 +1,88 @@
|
|
1
|
+
module Escort
|
2
|
+
module Formatter
|
3
|
+
class StreamOutputFormatter
|
4
|
+
DEFAULT_OUTPUT_WIDTH = 80
|
5
|
+
DEFAULT_INDENT_STRING = ' '
|
6
|
+
DEFAULT_INDENT = 0
|
7
|
+
|
8
|
+
attr_reader :stream, :indent_string, :current_indent, :max_output_width, :cursor_position
|
9
|
+
|
10
|
+
def initialize(stream = $stdout, options = {}, &block)
|
11
|
+
@stream = stream
|
12
|
+
@max_output_width = options[:max_output_width] || DEFAULT_OUTPUT_WIDTH
|
13
|
+
@indent_string = options[:indent_string] || DEFAULT_INDENT_STRING
|
14
|
+
@current_indent = options[:current_indent] || DEFAULT_INDENT
|
15
|
+
@cursor_position = CursorPosition.new(@max_output_width)
|
16
|
+
block.call(self) if block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
def print(string)
|
20
|
+
splitter_input = pad_string_to_account_for_cursor_position(string.to_s)
|
21
|
+
segments = StringSplitter.new(max_output_width).split(splitter_input)
|
22
|
+
segments = remove_padding_that_account_for_cursor_position(segments)
|
23
|
+
segments.each do |segment|
|
24
|
+
output_string = "#{current_indent_string}#{segment}"
|
25
|
+
output_string = segment unless cursor_position.newline?
|
26
|
+
stream.print output_string
|
27
|
+
cursor_position.update_for(segment)
|
28
|
+
newline if segments.last != segment
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def puts(string, options = {:newlines => 1})
|
33
|
+
print(string)
|
34
|
+
newline(options[:newlines])
|
35
|
+
end
|
36
|
+
|
37
|
+
def newline(newline_count = 1)
|
38
|
+
stream.print("\n" * newline_count)
|
39
|
+
cursor_position.reset if newline_count > 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def indent(count, &block)
|
43
|
+
newline unless cursor_position.newline?
|
44
|
+
new_indent = current_indent + count
|
45
|
+
self.class.new(stream, :max_output_width => max_output_width - count, :indent_string => indent_string, :current_indent => new_indent, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def grid(options = {}, &block)
|
49
|
+
if block_given?
|
50
|
+
options[:width] ||= max_output_width
|
51
|
+
grid = StringGrid.new(options, &block)
|
52
|
+
puts grid.to_s
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def pad_string_to_account_for_cursor_position(string)
|
59
|
+
"#{padding_string}#{string}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def remove_padding_that_account_for_cursor_position(segments)
|
63
|
+
first_string = segments.first
|
64
|
+
if first_string
|
65
|
+
segments[0] = first_string.sub(/#{padding_string}/, '')
|
66
|
+
end
|
67
|
+
segments
|
68
|
+
end
|
69
|
+
|
70
|
+
def padding_string
|
71
|
+
"." * cursor_position.position
|
72
|
+
end
|
73
|
+
|
74
|
+
def current_indent_width
|
75
|
+
current_indent_string.length
|
76
|
+
end
|
77
|
+
|
78
|
+
def current_indent_string
|
79
|
+
indent_string * current_indent
|
80
|
+
end
|
81
|
+
|
82
|
+
#def table(options = {}, &block)
|
83
|
+
#BorderlessTable.new(self, options).output(&block)
|
84
|
+
#newline(options[:newlines] || 1)
|
85
|
+
#end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -1,34 +1,35 @@
|
|
1
|
-
#TODO needs some serious testing stuffs
|
2
|
-
#split out into many classes VirtuaRow, PhysicalRow, FormatterRow
|
3
|
-
#needs method each_formatted_row
|
4
|
-
#the printing out should only happen in the outputter class not in the table intself
|
5
1
|
module Escort
|
6
2
|
module Formatter
|
7
|
-
class
|
8
|
-
|
3
|
+
class StringGrid
|
4
|
+
DEFAULT_WIDTH = 80
|
5
|
+
|
6
|
+
attr_reader :column_count, :width
|
9
7
|
attr_accessor :rows
|
10
8
|
|
11
|
-
def initialize(
|
12
|
-
@
|
9
|
+
def initialize(options = {}, &block)
|
10
|
+
@width = options[:width] || DEFAULT_WIDTH
|
13
11
|
@column_count = options[:columns] || 3
|
14
12
|
@rows = []
|
13
|
+
block.call(self) if block_given?
|
15
14
|
end
|
16
15
|
|
17
16
|
def row(*column_values)
|
17
|
+
while column_values.size < @column_count
|
18
|
+
column_values << ''
|
19
|
+
end
|
18
20
|
rows << column_values.map(&:to_s)
|
19
|
-
#TODO raise error if column values size doesn't match columns
|
20
21
|
end
|
21
22
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
23
|
+
def to_s
|
24
|
+
buffer = []
|
25
25
|
rows.each do |cells|
|
26
26
|
virtual_row = normalize_virtual_row(virtual_row_for(cells))
|
27
27
|
physical_row_count_for(virtual_row).times do |physical_count|
|
28
28
|
physical_row = format_physical_row_values(physical_row_for(virtual_row, physical_count))
|
29
|
-
|
29
|
+
buffer << physical_row.join("").chomp
|
30
30
|
end
|
31
31
|
end
|
32
|
+
buffer.join("\n")
|
32
33
|
end
|
33
34
|
|
34
35
|
private
|
@@ -65,7 +66,6 @@ module Escort
|
|
65
66
|
end
|
66
67
|
|
67
68
|
def column_width(column_index)
|
68
|
-
#TODO raise error if index out of bounds
|
69
69
|
width = fair_column_width(column_index)
|
70
70
|
if column_index == column_count - 1
|
71
71
|
width = last_column_width
|
@@ -74,14 +74,13 @@ module Escort
|
|
74
74
|
end
|
75
75
|
|
76
76
|
def fair_column_width(index)
|
77
|
-
#TODO raise error if index out of bounds
|
78
77
|
width = values_in_column(index).map(&:length).max
|
79
78
|
width = width + 1
|
80
79
|
width > max_column_width ? max_column_width : width
|
81
80
|
end
|
82
81
|
|
83
82
|
def last_column_width
|
84
|
-
full_fair_column_width = max_column_width * column_count
|
83
|
+
full_fair_column_width = max_column_width * column_count + max_column_width_remainder
|
85
84
|
all_but_last_fair_column_width = 0
|
86
85
|
(column_count - 1).times do |index|
|
87
86
|
all_but_last_fair_column_width += fair_column_width(index)
|
@@ -90,16 +89,19 @@ module Escort
|
|
90
89
|
end
|
91
90
|
|
92
91
|
def values_in_column(column_index)
|
93
|
-
#TODO raise error if index out of bounds
|
94
92
|
rows.map{|cells| cells[column_index]}
|
95
93
|
end
|
96
94
|
|
97
95
|
def max_column_width
|
98
|
-
|
96
|
+
width/column_count
|
97
|
+
end
|
98
|
+
|
99
|
+
def max_column_width_remainder
|
100
|
+
width%column_count
|
99
101
|
end
|
100
102
|
|
101
103
|
def cell_value(value, column_index)
|
102
|
-
sprintf("%-#{column_width(column_index)}s", value
|
104
|
+
sprintf("%-#{column_width(column_index)}s", value)
|
103
105
|
end
|
104
106
|
end
|
105
107
|
end
|
@@ -1,18 +1,38 @@
|
|
1
1
|
module Escort
|
2
2
|
module Formatter
|
3
3
|
class StringSplitter
|
4
|
-
attr_reader :max_segment_width
|
4
|
+
attr_reader :max_segment_width #, :first_segment_max_length
|
5
5
|
|
6
|
-
def initialize(max_segment_width)
|
6
|
+
def initialize(max_segment_width, options = {})
|
7
7
|
@max_segment_width = max_segment_width
|
8
|
+
#@first_segment_max_length = options[:first_segment_max_length] || max_segment_width
|
8
9
|
end
|
9
10
|
|
10
|
-
def split(
|
11
|
-
|
11
|
+
def split(input_string)
|
12
|
+
input_strings = input_string.split("\n")
|
13
|
+
[split_strings(input_strings)].flatten
|
14
|
+
#first_string = strings.shift
|
15
|
+
#other_strings = strings
|
16
|
+
#result = [split_first_string(first_string) + split_strings(other_strings)].flatten
|
17
|
+
#result
|
12
18
|
end
|
13
19
|
|
14
20
|
private
|
15
21
|
|
22
|
+
#def split_first_string(string)
|
23
|
+
#if first_segment_max_length >= string.length
|
24
|
+
#split_string(string)
|
25
|
+
#else
|
26
|
+
#first = string.slice(0, first_segment_max_length)
|
27
|
+
#last = string.slice(first_segment_max_length..-1)
|
28
|
+
#split_strings([first, last])
|
29
|
+
#end
|
30
|
+
#end
|
31
|
+
|
32
|
+
def split_strings(strings)
|
33
|
+
strings.map { |s| split_string(s) }
|
34
|
+
end
|
35
|
+
|
16
36
|
def split_string(string)
|
17
37
|
result = []
|
18
38
|
if string.length > max_segment_width
|
@@ -19,7 +19,7 @@ module Escort
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def default_config_path
|
22
|
-
@default_config_path ||= File.join(File.expand_path(ENV["HOME"]), config_filename)
|
22
|
+
@default_config_path ||= (config_filename ? File.join(File.expand_path(ENV["HOME"]), config_filename) : nil)
|
23
23
|
end
|
24
24
|
|
25
25
|
private
|
@@ -29,7 +29,13 @@ module Escort
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def config_path
|
32
|
-
@config_path ||= (auto_options.non_default_config_path ||
|
32
|
+
@config_path ||= (auto_options.non_default_config_path || locator.locate || default_config_path)
|
33
|
+
end
|
34
|
+
|
35
|
+
def locator
|
36
|
+
Locator::Chaining.new(config_filename).
|
37
|
+
add_locator(Locator::ExecutingScriptDirectory.new(config_filename)).
|
38
|
+
add_locator(Locator::DescendingToHome.new(config_filename))
|
33
39
|
end
|
34
40
|
end
|
35
41
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Escort
|
2
|
+
module Setup
|
3
|
+
module Configuration
|
4
|
+
module Locator
|
5
|
+
class Chaining < Base
|
6
|
+
attr_reader :locators
|
7
|
+
|
8
|
+
def initialize(filename, locators = [])
|
9
|
+
super(filename)
|
10
|
+
@locators = locators || []
|
11
|
+
end
|
12
|
+
|
13
|
+
def locate
|
14
|
+
locators.each do |locator|
|
15
|
+
filepath = locator.locate
|
16
|
+
return filepath if filepath
|
17
|
+
end
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_locator(locator)
|
22
|
+
@locators << locator
|
23
|
+
self
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Escort
|
2
|
+
module Setup
|
3
|
+
module Configuration
|
4
|
+
module Locator
|
5
|
+
class ExecutingScriptDirectory < Base
|
6
|
+
def locate
|
7
|
+
location_directory = File.dirname($0)
|
8
|
+
filepath = File.expand_path(File.join(location_directory, filename))
|
9
|
+
File.exists?(filepath) ? filepath : nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Escort
|
2
|
+
module Setup
|
3
|
+
module Configuration
|
4
|
+
module Locator
|
5
|
+
class SpecifiedDirectory < Base
|
6
|
+
attr_reader :location_directory
|
7
|
+
|
8
|
+
def initialize(filename, location_directory)
|
9
|
+
super(filename)
|
10
|
+
@location_directory = location_directory
|
11
|
+
end
|
12
|
+
|
13
|
+
def locate
|
14
|
+
filepath = File.expand_path(File.join(location_directory, filename))
|
15
|
+
File.exists?(filepath) ? filepath : nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
1
3
|
module Escort
|
2
4
|
module Setup
|
3
5
|
module Configuration
|
@@ -20,11 +22,11 @@ module Escort
|
|
20
22
|
data = {}
|
21
23
|
begin
|
22
24
|
json = File.read(path)
|
23
|
-
hash = JSON.parse(json)
|
25
|
+
hash = ::JSON.parse(json)
|
24
26
|
data = Escort::Utils.symbolize_keys(hash)
|
25
27
|
rescue => e
|
26
28
|
error_logger.warn { "Found config at #{path}, but failed to load it, perhaps your JSON syntax is invalid. Attempting to continue without..." }
|
27
|
-
error_logger.
|
29
|
+
error_logger.debug(e)
|
28
30
|
end
|
29
31
|
data
|
30
32
|
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'fileutils'
|
3
|
+
|
1
4
|
module Escort
|
2
5
|
module Setup
|
3
6
|
module Configuration
|
@@ -32,8 +35,9 @@ module Escort
|
|
32
35
|
|
33
36
|
def save_to_file
|
34
37
|
current_path = File.expand_path(path)
|
35
|
-
File.
|
36
|
-
|
38
|
+
FileUtils.mkdir_p(File.dirname(current_path))
|
39
|
+
File.open(current_path, "w") do |f|
|
40
|
+
f.puts ::JSON.pretty_generate(data)
|
37
41
|
end
|
38
42
|
end
|
39
43
|
end
|
@@ -4,7 +4,6 @@ module Escort
|
|
4
4
|
class Command
|
5
5
|
def initialize(name, options = {}, &block)
|
6
6
|
reset(name)
|
7
|
-
@name = name
|
8
7
|
@description = options[:description] || options[:desc] || ""
|
9
8
|
@aliases = [options[:aliases] || []].flatten
|
10
9
|
@requires_arguments ||= options[:requires_arguments]
|
@@ -38,10 +37,6 @@ module Escort
|
|
38
37
|
end
|
39
38
|
end
|
40
39
|
|
41
|
-
def conflicting_options(*command_names)
|
42
|
-
raise Escort::ClientError.new("This interface for specifying conflicting options is no longer supported, please use 'opts.conflict' in the options block")
|
43
|
-
end
|
44
|
-
|
45
40
|
def summary(summary)
|
46
41
|
@summary = summary
|
47
42
|
end
|
@@ -53,14 +48,18 @@ module Escort
|
|
53
48
|
private
|
54
49
|
|
55
50
|
def reset(name)
|
51
|
+
@name = name
|
52
|
+
@summary = nil
|
53
|
+
@description = nil
|
56
54
|
@requires_arguments = false
|
57
55
|
@commands = {}
|
58
56
|
@options = Options.new(name)
|
59
57
|
@action = Action.new(name)
|
60
|
-
|
61
|
-
|
58
|
+
custom_reset
|
59
|
+
end
|
60
|
+
|
61
|
+
def custom_reset
|
62
62
|
@aliases = []
|
63
|
-
@summary = nil
|
64
63
|
end
|
65
64
|
end
|
66
65
|
end
|
@@ -1,39 +1,14 @@
|
|
1
1
|
module Escort
|
2
2
|
module Setup
|
3
3
|
module Dsl
|
4
|
-
class Global
|
4
|
+
class Global < Command
|
5
5
|
def initialize(&block)
|
6
|
-
reset
|
6
|
+
reset(:global)
|
7
7
|
block.call(self)
|
8
8
|
rescue => e
|
9
9
|
raise Escort::ClientError.new("Problem with syntax of global configuration", e)
|
10
10
|
end
|
11
11
|
|
12
|
-
def options(&block)
|
13
|
-
Options.options(:global, @options, &block)
|
14
|
-
end
|
15
|
-
|
16
|
-
def action(&block)
|
17
|
-
Action.action(:global, @action, &block)
|
18
|
-
end
|
19
|
-
|
20
|
-
def command(name, options = {}, &block)
|
21
|
-
options[:requires_arguments] = @requires_arguments
|
22
|
-
command = Command.new(name.to_sym, options, &block)
|
23
|
-
aliases = [options[:aliases] || []].flatten + [name]
|
24
|
-
aliases.each do |name|
|
25
|
-
@commands[name.to_sym] = command
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def requires_arguments(boolean = true)
|
30
|
-
raise Escort::ClientError.new("Parameter to 'requires_arguments' must be a boolean") unless [true, false].include?(boolean)
|
31
|
-
@requires_arguments = boolean
|
32
|
-
@commands.each do |command|
|
33
|
-
command.requires_arguments(boolean)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
12
|
def config_file(name, options = {})
|
38
13
|
@config_file = ConfigFile.new(name, options)
|
39
14
|
end
|
@@ -42,35 +17,12 @@ module Escort
|
|
42
17
|
@version = version
|
43
18
|
end
|
44
19
|
|
45
|
-
def summary(summary)
|
46
|
-
@summary = summary
|
47
|
-
end
|
48
|
-
|
49
|
-
def description(description)
|
50
|
-
@description = description
|
51
|
-
end
|
52
|
-
|
53
|
-
def conflicting_options(*command_names)
|
54
|
-
raise Escort::ClientError.new("This interface for specifying conflicting options is no longer supported, please use 'opts.conflict' in the options block")
|
55
|
-
end
|
56
|
-
|
57
20
|
private
|
58
21
|
|
59
|
-
def
|
22
|
+
def custom_reset
|
60
23
|
@version = nil
|
61
|
-
@summary = nil
|
62
|
-
@description = nil
|
63
|
-
@commands = {}
|
64
|
-
@requires_arguments = false
|
65
|
-
@options = Options.new
|
66
|
-
@action = Action.new
|
67
24
|
@config_file = nil
|
68
25
|
end
|
69
|
-
|
70
|
-
def set_instance_variable_on(instance, instance_variable, value)
|
71
|
-
instance_variable_symbol = :"@#{instance_variable.to_s}"
|
72
|
-
instance.instance_variable_set(instance_variable_symbol, value)
|
73
|
-
end
|
74
26
|
end
|
75
27
|
end
|
76
28
|
end
|