escort 0.2.1 → 0.3.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 +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
|