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
@@ -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