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
@@ -0,0 +1,59 @@
1
+ describe Escort::Formatter::StringGrid do
2
+ let(:width) {20}
3
+
4
+ describe "#to_s" do
5
+ subject {grid.to_s}
6
+
7
+ context "when 2 rows and 3 columns" do
8
+ context "no strings span multiple rows" do
9
+ let(:grid) do
10
+ Escort::Formatter::StringGrid.new(:columns => 3, :width => width) do |g|
11
+ g.row 'a', 'b', 'c'
12
+ g.row 1, 2, 3
13
+ end
14
+ end
15
+ it("all rows should equal table width") do
16
+ subject.split("\n").each do |val|
17
+ val.length.should == width
18
+ end
19
+ end
20
+ it {subject.should == "a b c \n1 2 3 "}
21
+ end
22
+
23
+ context "first column string spans 4 rows" do
24
+ let(:grid) do
25
+ Escort::Formatter::StringGrid.new(:columns => 3, :width => width) do |g|
26
+ g.row '123456789abcdefgh', 'b', 'c'
27
+ g.row 1, 2, 3
28
+ end
29
+ end
30
+ it("should have 5 rows") do
31
+ subject.split("\n").size.should == 5
32
+ end
33
+ it("all rows should equal table width") do
34
+ subject.split("\n").each do |val|
35
+ val.length.should == width
36
+ end
37
+ end
38
+ it { subject.should == "12345 b c \n6789a \nbcdef \ngh \n1 2 3 " }
39
+ end
40
+ context "last column string spans 2 rows" do
41
+ let(:grid) do
42
+ Escort::Formatter::StringGrid.new(:columns => 3, :width => width) do |g|
43
+ g.row 'a', 'b', 'c'
44
+ g.row 1, 2, "123456789abcdefghijk"
45
+ end
46
+ end
47
+ it("should have 3 rows") do
48
+ subject.split("\n").size.should == 3
49
+ end
50
+ it("all rows should equal table width") do
51
+ subject.split("\n").each do |val|
52
+ val.length.should == width
53
+ end
54
+ end
55
+ it { subject.should == "a b c \n1 2 123456789abcdef \n ghijk " }
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,101 @@
1
+ describe Escort::Setup::Configuration::Generator do
2
+
3
+ let(:generator) {Escort::Setup::Configuration::Generator.new(setup)}
4
+ let(:setup) { Escort::SetupAccessor.new(app_configuration) }
5
+
6
+
7
+ describe "#default_data" do
8
+ subject {generator.default_data}
9
+
10
+ context "when basic configuration" do
11
+ let(:app_configuration) do
12
+ Escort::Setup::Dsl::Global.new do |app|
13
+ app.options do |opts|
14
+ opts.opt :option1, "option1", :short => :none, :long => '--option1', :type => :string
15
+ end
16
+
17
+ app.action do |options, arguments|
18
+ end
19
+ end
20
+ end
21
+ it("should have a user config") {subject[:user].should_not be_nil}
22
+ it("user config should be empty") {subject[:user].should be_empty}
23
+ it("shoud have a global config") {subject[:global].should_not be_nil}
24
+ it("global config should not be empty") {subject[:global].should_not be_empty}
25
+ it("should have a global commands config") {subject[:global][:commands].should_not be_nil}
26
+ it("global commands config should be empty") {subject[:global][:commands].should be_empty}
27
+ it("should have a global options config") {subject[:global][:options].should_not be_nil}
28
+ it("global options config should not be empty") {subject[:global][:options].should_not be_empty}
29
+ it("options config should have a key for configured option") {subject[:global][:options].keys.size.should == 1}
30
+ it("options config key for configured config should be nil when no default value") {subject[:global][:options][:option1].should be_nil}
31
+
32
+ context "and configured option has default value" do
33
+ let(:app_configuration) do
34
+ Escort::Setup::Dsl::Global.new do |app|
35
+ app.options do |opts|
36
+ opts.opt :option1, "option1", :short => :none, :long => '--option1', :type => :string, :default => "hello"
37
+ end
38
+
39
+ app.action do |options, arguments|
40
+ end
41
+ end
42
+ end
43
+ it("options config key for configured config should equal to default value") {subject[:global][:options][:option1].should == 'hello'}
44
+ end
45
+ end
46
+
47
+ context "when suite configuration" do
48
+ let(:app_configuration) do
49
+ Escort::Setup::Dsl::Global.new do |app|
50
+ app.options do |opts|
51
+ opts.opt :option1, "option1", :short => :none, :long => '--option1', :type => :string
52
+ end
53
+
54
+ app.command :command1, :aliases => [:c1, :com1] do |command|
55
+ command.options do |opts|
56
+ opts.opt :option2, "option2", :short => :none, :long => '--option2', :type => :string, :default => 'blah'
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ it("should have a global commands config") {subject[:global][:commands].should_not be_nil}
63
+ it("global commands config should be empty") {subject[:global][:commands].should_not be_empty}
64
+ it("command1 commands config should not be nil") {subject[:global][:commands][:command1][:commands].should_not be_nil}
65
+ it("command1 commands config should be empty") {subject[:global][:commands][:command1][:commands].should be_empty}
66
+ it("command1 options config should not be nil") {subject[:global][:commands][:command1][:options].should_not be_nil}
67
+ it("command1 options config should not be empty") {subject[:global][:commands][:command1][:options].should_not be_empty}
68
+ it("command1 options option value should be the same as default value") {subject[:global][:commands][:command1][:options][:option2].should == 'blah'}
69
+ end
70
+
71
+ context "when suite with sub commands configuration" do
72
+ let(:app_configuration) do
73
+ Escort::Setup::Dsl::Global.new do |app|
74
+ app.options do |opts|
75
+ opts.opt :option1, "option1", :short => :none, :long => '--option1', :type => :string
76
+ end
77
+
78
+ app.command :command1, :aliases => [:c1, :com1] do |command|
79
+ command.options do |opts|
80
+ opts.opt :option2, "option2", :short => :none, :long => '--option2', :type => :string, :default => 'blah'
81
+ end
82
+
83
+ command.command :sub_command1 do |command|
84
+ command.options do |opts|
85
+ opts.opt :option3, "option3", :short => :none, :long => '--option3', :type => :string, :default => 'yadda'
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ it("command1 commands config should not be nil") {subject[:global][:commands][:command1][:commands].should_not be_nil}
93
+ it("command1 commands config should not be empty") {subject[:global][:commands][:command1][:commands].should_not be_empty}
94
+ it("sub_command1 command config should not be nil") {subject[:global][:commands][:command1][:commands][:sub_command1][:commands].should_not be_nil}
95
+ it("sub_command1 command config should be empty") {subject[:global][:commands][:command1][:commands][:sub_command1][:commands].should be_empty}
96
+ it("sub_command1 options config should not be nil") {subject[:global][:commands][:command1][:commands][:sub_command1][:options].should_not be_nil}
97
+ it("sub_command1 options config should not be empty") {subject[:global][:commands][:command1][:commands][:sub_command1][:options].should_not be_empty}
98
+ it("sub_command1 options option value should be the same as default value") {subject[:global][:commands][:command1][:commands][:sub_command1][:options][:option3].should == 'yadda'}
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,79 @@
1
+ describe Escort::Setup::Configuration::Loader do
2
+ include FakeFS::SpecHelpers
3
+
4
+ let(:loader) {Escort::Setup::Configuration::Loader.new(setup, auto_options)}
5
+ let(:setup) { Escort::SetupAccessor.new(app_configuration) }
6
+ let(:auto_options) {Escort::AutoOptions.new({})}
7
+
8
+ describe "#default_config_path" do
9
+ subject {loader.default_config_path}
10
+
11
+ context "when setup has config file" do
12
+ let(:app_configuration) do
13
+ Escort::Setup::Dsl::Global.new do |app|
14
+ app.config_file '.test1rc'
15
+ app.options do |opts|
16
+ opts.opt :option1, "option1", :short => :none, :long => '--option1', :type => :string
17
+ end
18
+ end
19
+ end
20
+ it {subject.should == File.join(File.expand_path('~'), '.test1rc')}
21
+ end
22
+
23
+ context "when setup has no config file" do
24
+ let(:app_configuration) do
25
+ Escort::Setup::Dsl::Global.new do |app|
26
+ app.options do |opts|
27
+ opts.opt :option1, "option1", :short => :none, :long => '--option1', :type => :string
28
+ end
29
+ end
30
+ end
31
+ it {subject.should be_nil }
32
+ end
33
+ end
34
+
35
+ describe "#configuration" do
36
+ subject {loader.configuration}
37
+
38
+
39
+ context "when setup has config file" do
40
+ context "and config file is autocreatable" do
41
+ let(:app_configuration) do
42
+ Escort::Setup::Dsl::Global.new do |app|
43
+ app.config_file '.test1rc', :autocreate => true
44
+ app.options do |opts|
45
+ opts.opt :option1, "option1", :short => :none, :long => '--option1', :type => :string
46
+ end
47
+ end
48
+ end
49
+ it {subject.should_not be_nil}
50
+ it {subject.should_not be_empty}
51
+ it {subject; File.should exist File.join(File.expand_path('~'), '.test1rc') }
52
+ end
53
+
54
+ context "and config file is not autocreatable" do
55
+ let(:app_configuration) do
56
+ Escort::Setup::Dsl::Global.new do |app|
57
+ app.config_file '.test1rc'
58
+ app.options do |opts|
59
+ opts.opt :option1, "option1", :short => :none, :long => '--option1', :type => :string
60
+ end
61
+ end
62
+ end
63
+ it {subject.should_not be_nil}
64
+ it {subject.should be_empty}
65
+ end
66
+ end
67
+
68
+ context "when setup has no config file" do
69
+ let(:app_configuration) do
70
+ Escort::Setup::Dsl::Global.new do |app|
71
+ app.options do |opts|
72
+ opts.opt :option1, "option1", :short => :none, :long => '--option1', :type => :string
73
+ end
74
+ end
75
+ end
76
+ it {subject.should be_empty}
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,81 @@
1
+ describe Escort::Setup::Configuration::Locator::Chaining do
2
+ include FakeFS::SpecHelpers
3
+
4
+ let(:locator) {Escort::Setup::Configuration::Locator::Chaining.new(filename)}
5
+ let(:filename) {'.blahrc'}
6
+ let(:home) {File.expand_path('~')}
7
+ let(:pwd) {File.expand_path(File.join(home, 'blah', 'yadda', 'foo'))}
8
+ let(:executing_directory) {File.expand_path(File.dirname($0))}
9
+
10
+ let(:descending_locator) {Escort::Setup::Configuration::Locator::DescendingToHome.new(filename)}
11
+ let(:executing_script_locator) {Escort::Setup::Configuration::Locator::ExecutingScriptDirectory.new(filename)}
12
+
13
+ before do
14
+ FileUtils.mkdir_p(executing_directory)
15
+ FileUtils.mkdir_p(home)
16
+ FileUtils.mkdir_p(pwd)
17
+ Dir.chdir(pwd)
18
+ end
19
+
20
+ describe "#add_locator" do
21
+ subject {locator.add_locator("blah")}
22
+ it {subject.should == locator}
23
+ it {subject.locators.size.should == 1}
24
+ end
25
+
26
+ describe "#locate" do
27
+ subject {locator.locate}
28
+
29
+ context "when no sub-locators specified" do
30
+ it {subject.should be_nil}
31
+ end
32
+
33
+ context "when executing directory locator specified" do
34
+ before do
35
+ locator.add_locator(executing_script_locator)
36
+ end
37
+
38
+ let(:path) {File.join(executing_directory, filename)}
39
+
40
+ context "and file should be found by it" do
41
+ before do
42
+ File.open(path, 'w') {|f| f.write("hello") }
43
+ end
44
+
45
+ it {subject.should == path}
46
+ end
47
+
48
+ context "and file should not be found by it" do
49
+ it {subject.should be_nil}
50
+ end
51
+ end
52
+
53
+ context "when descending and current script locators are specified" do
54
+ before do
55
+ locator.
56
+ add_locator(descending_locator).
57
+ add_locator(executing_script_locator)
58
+ end
59
+
60
+ context "and file should not be found" do
61
+ it {subject.should be_nil}
62
+ end
63
+
64
+ context "and file should be found by current script locator" do
65
+ let(:path) {File.join(executing_directory, filename)}
66
+ before do
67
+ File.open(path, 'w') {|f| f.write("hello") }
68
+ end
69
+ it {subject.should == path}
70
+ end
71
+
72
+ context "and file should be found by descending locator"do
73
+ let(:path) {File.join(home, filename)}
74
+ before do
75
+ File.open(path, 'w') {|f| f.write("hello") }
76
+ end
77
+ it {subject.should == path}
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,57 @@
1
+ describe Escort::Setup::Configuration::Locator::DescendingToHome do
2
+ include FakeFS::SpecHelpers
3
+
4
+ let(:locator) {Escort::Setup::Configuration::Locator::DescendingToHome.new(filename)}
5
+ let(:filename) {'.blahrc'}
6
+ let(:home) {File.expand_path('~')}
7
+ let(:pwd) {File.expand_path(File.join(home, 'blah', 'yadda', 'foo'))}
8
+
9
+ before do
10
+ FileUtils.mkdir_p(home)
11
+ FileUtils.mkdir_p(pwd)
12
+ Dir.chdir(pwd)
13
+ end
14
+
15
+ describe "#locate" do
16
+ subject {locator.locate}
17
+
18
+ context "when file does not exist anywhere in the relevant directories" do
19
+ it {subject.should be_nil}
20
+ end
21
+
22
+ context "when file exists" do
23
+ context "in the top directory of search" do
24
+ let(:file_location) {File.expand_path(File.join(home, 'blah', 'yadda', 'foo'))}
25
+ let(:path) {File.join(file_location, filename)}
26
+
27
+ before do
28
+ File.open(path, 'w') {|f| f.write("hello") }
29
+ end
30
+
31
+ it {subject.should == path}
32
+ end
33
+
34
+ context "in one of the middle directories of search" do
35
+ let(:file_location) {File.expand_path(File.join(home, 'blah', 'yadda'))}
36
+ let(:path) {File.join(file_location, filename)}
37
+
38
+ before do
39
+ File.open(path, 'w') {|f| f.write("hello") }
40
+ end
41
+
42
+ it {subject.should == path}
43
+ end
44
+
45
+ context "in the home directory" do
46
+ let(:file_location) {File.expand_path(home)}
47
+ let(:path) {File.join(file_location, filename)}
48
+
49
+ before do
50
+ File.open(path, 'w') {|f| f.write("hello") }
51
+ end
52
+
53
+ it {subject.should == path}
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,29 @@
1
+ describe Escort::Setup::Configuration::Locator::ExecutingScriptDirectory do
2
+ include FakeFS::SpecHelpers
3
+
4
+ let(:locator) {Escort::Setup::Configuration::Locator::ExecutingScriptDirectory.new(filename)}
5
+ let(:filename) {'.blahrc'}
6
+ let(:directory) {File.expand_path(File.dirname($0))}
7
+ let(:path) {File.join(directory, filename)}
8
+
9
+ describe "#locate" do
10
+ subject {locator.locate}
11
+
12
+ context "when file does not exist in directory" do
13
+ before do
14
+ FileUtils.mkdir_p(directory)
15
+ end
16
+
17
+ it {subject.should be_nil}
18
+ end
19
+
20
+ context "when file exists in directory" do
21
+ before do
22
+ FileUtils.mkdir_p(directory)
23
+ File.open(path, 'w') {|f| f.write("hello") }
24
+ end
25
+
26
+ it {subject.should == path}
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ describe Escort::Setup::Configuration::Locator::SpecifiedDirectory do
2
+ include FakeFS::SpecHelpers
3
+
4
+ let(:locator) {Escort::Setup::Configuration::Locator::SpecifiedDirectory.new(filename, directory)}
5
+ let(:filename) {'.blahrc'}
6
+ let(:directory) {'/usr/alan/yadda'}
7
+ let(:path) {File.join(directory, filename)}
8
+
9
+ describe "#locate" do
10
+ subject {locator.locate}
11
+
12
+ context "when directory does not exist" do
13
+ it {subject.should be_nil}
14
+ end
15
+
16
+ context "when file does not exist in directory" do
17
+ before do
18
+ FileUtils.mkdir_p(directory)
19
+ end
20
+
21
+ it {subject.should be_nil}
22
+ end
23
+
24
+ context "when file exists in directory" do
25
+ before do
26
+ FileUtils.mkdir_p(directory)
27
+ File.open(path, 'w') {|f| f.write("hello") }
28
+ end
29
+
30
+ it {subject.should == path}
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,41 @@
1
+ describe Escort::Setup::Configuration::MergeTool do
2
+ let(:merge_tool) {Escort::Setup::Configuration::MergeTool.new(new_config_hash, old_config_hash)}
3
+
4
+ describe "#config_hash" do
5
+ subject {merge_tool.config_hash}
6
+
7
+ context "when there is a new command" do
8
+ let(:old_config_hash) { {:global => {:commands =>{}, :options => {:option1 => nil}}, :user => {:hello => :world}} }
9
+ let(:new_config_hash) { {:global => {:commands =>{:command1 => {:commands => {}, :options => {}}}, :options => {}}, :user => {}} }
10
+
11
+ it("should be part of the config"){subject[:global][:commands][:command1].should_not be_nil}
12
+ end
13
+
14
+ context "when there is a new option for an old command" do
15
+ let(:old_config_hash) { {:global => {:commands =>{}, :options => {:option1 => nil}}, :user => {:hello => :world}} }
16
+ let(:new_config_hash) { {:global => {:commands =>{}, :options => {:option1 => nil, :option2 => nil}}, :user => {}} }
17
+
18
+ it("should be part of the config"){subject[:global][:options].keys.should include(:option2) }
19
+ end
20
+
21
+ context "when an old option for a command is no longer there" do
22
+ let(:old_config_hash) { {:global => {:commands =>{}, :options => {:option1 => nil}}, :user => {:hello => :world}} }
23
+ let(:new_config_hash) { {:global => {:commands =>{}, :options => {:option2 => nil}}, :user => {}} }
24
+
25
+ it("should not be part of the config"){subject[:global][:options].keys.should_not include(:option1) }
26
+ end
27
+
28
+ context "when old config hash has user supplied value for option" do
29
+ let(:old_config_hash) { {:global => {:commands =>{}, :options => {:option1 => :hello}}, :user => {:hello => :world}} }
30
+ let(:new_config_hash) { {:global => {:commands =>{}, :options => {:option1 => nil}}, :user => {}} }
31
+
32
+ it("should retain the user supplied value for option"){subject[:global][:options][:option1].should == :hello }
33
+ end
34
+
35
+ context "when old user hash has values" do
36
+ let(:old_config_hash) { {:global => {:commands =>{}, :options => {}}, :user => {:hello => :world}} }
37
+ let(:new_config_hash) { {:global => {:commands =>{}, :options => {}}, :user => {}} }
38
+ it("should retain the user config values"){subject[:user][:hello].should == :world}
39
+ end
40
+ end
41
+ end