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.
Files changed (69) hide show
  1. checksums.yaml +8 -8
  2. data/.irbrc +2 -0
  3. data/.travis.yml +1 -1
  4. data/README.md +272 -3
  5. data/TODO.md +118 -71
  6. data/examples/.my_apprc +24 -0
  7. data/examples/basic_config_file +16 -0
  8. data/examples/basic_conflicts +1 -1
  9. data/examples/basic_with_everything +30 -0
  10. data/examples/suite_complex +65 -0
  11. data/examples/{command → suite_simple} +0 -0
  12. data/examples/suite_with_sub_commands +94 -0
  13. data/lib/escort.rb +6 -4
  14. data/lib/escort/action_command/base.rb +7 -5
  15. data/lib/escort/app.rb +2 -8
  16. data/lib/escort/auto_options.rb +5 -3
  17. data/lib/escort/formatter/cursor_position.rb +29 -0
  18. data/lib/escort/formatter/default_help_formatter.rb +37 -32
  19. data/lib/escort/formatter/option.rb +1 -1
  20. data/lib/escort/formatter/stream_output_formatter.rb +88 -0
  21. data/lib/escort/formatter/{borderless_table.rb → string_grid.rb} +21 -19
  22. data/lib/escort/formatter/string_splitter.rb +24 -4
  23. data/lib/escort/setup/configuration/loader.rb +8 -2
  24. data/lib/escort/setup/configuration/locator/chaining.rb +29 -0
  25. data/lib/escort/setup/configuration/locator/executing_script_directory.rb +15 -0
  26. data/lib/escort/setup/configuration/locator/specified_directory.rb +21 -0
  27. data/lib/escort/setup/configuration/reader.rb +4 -2
  28. data/lib/escort/setup/configuration/writer.rb +6 -2
  29. data/lib/escort/setup/dsl/command.rb +7 -8
  30. data/lib/escort/setup/dsl/global.rb +3 -51
  31. data/lib/escort/version.rb +1 -1
  32. data/spec/integration/basic_config_file_spec.rb +82 -0
  33. data/spec/integration/suite_simple_spec.rb +45 -0
  34. data/spec/integration/suite_sub_command_spec.rb +51 -0
  35. data/spec/lib/escort/action_command/base_spec.rb +200 -0
  36. data/spec/lib/escort/formatter/option_spec.rb +2 -2
  37. data/spec/lib/escort/formatter/stream_output_formatter_spec.rb +214 -0
  38. data/spec/lib/escort/formatter/string_grid_spec.rb +59 -0
  39. data/spec/lib/escort/setup/configuration/generator_spec.rb +101 -0
  40. data/spec/lib/escort/setup/configuration/loader_spec.rb +79 -0
  41. data/spec/lib/escort/setup/configuration/locator/chaining_spec.rb +81 -0
  42. data/spec/lib/escort/setup/configuration/locator/descending_to_home_spec.rb +57 -0
  43. data/spec/lib/escort/setup/configuration/locator/executing_script_directory_spec.rb +29 -0
  44. data/spec/lib/escort/setup/configuration/locator/specified_directory_spec.rb +33 -0
  45. data/spec/lib/escort/setup/configuration/merge_tool_spec.rb +41 -0
  46. data/spec/lib/escort/setup/configuration/reader_spec.rb +41 -0
  47. data/spec/lib/escort/setup/configuration/writer_spec.rb +75 -0
  48. data/spec/spec_helper.rb +2 -1
  49. metadata +44 -24
  50. data/examples/attic/1_1_basic.rb +0 -15
  51. data/examples/attic/1_2_basic_requires_arguments.rb +0 -15
  52. data/examples/attic/2_2_command.rb +0 -18
  53. data/examples/attic/2_2_command_requires_arguments.rb +0 -20
  54. data/examples/attic/2_3_nested_commands.rb +0 -26
  55. data/examples/attic/3_validations.rb +0 -31
  56. data/examples/attic/4_1_config_file.rb +0 -42
  57. data/examples/attic/argument_handling/basic.rb +0 -12
  58. data/examples/attic/argument_handling/basic_command.rb +0 -18
  59. data/examples/attic/argument_handling/no_arguments.rb +0 -14
  60. data/examples/attic/argument_handling/no_arguments_command.rb +0 -20
  61. data/examples/attic/command_aliases/app.rb +0 -31
  62. data/examples/attic/config_file/.apprc2 +0 -16
  63. data/examples/attic/config_file/app.rb +0 -78
  64. data/examples/attic/config_file/sub_commands.rb +0 -35
  65. data/examples/attic/default_command/app.rb +0 -20
  66. data/examples/attic/sub_commands/app.rb +0 -18
  67. data/examples/attic/validation_basic/app.rb +0 -31
  68. data/lib/escort/formatter/terminal_formatter.rb +0 -58
  69. data/lib/escort/setup/dsl/validations.rb +0 -25
@@ -39,7 +39,7 @@ module Escort
39
39
  end
40
40
 
41
41
  def validations
42
- has_validations? ? "#{validation_messages.join("\n- ")}" : ''
42
+ has_validations? ? validation_messages : []
43
43
  end
44
44
 
45
45
  private
@@ -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 BorderlessTable
8
- attr_reader :column_count, :formatter
3
+ class StringGrid
4
+ DEFAULT_WIDTH = 80
5
+
6
+ attr_reader :column_count, :width
9
7
  attr_accessor :rows
10
8
 
11
- def initialize(formatter, options = {})
12
- @formatter = formatter
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 output(&block)
23
- block.call(self)
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
- formatter.put physical_row.join("").chomp, :newlines => 1
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
- (formatter.terminal_columns - 1 - formatter.current_indent_string.length)/column_count
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.strip)
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(string)
11
- string.split("\n").map { |s| split_string(s) }.flatten
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 || Locator::DescendingToHome.new(config_filename).locate || 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.error(e)
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.open(current_path,"w") do |f|
36
- f.puts JSON.pretty_generate(data)
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
- @name = nil
61
- @description = nil
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 reset
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