escort 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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