hammer_cli 0.0.12 → 0.0.13

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,18 +3,105 @@ require 'readline'
3
3
 
4
4
  module HammerCLI
5
5
 
6
+ class ShellMainCommand < AbstractCommand
7
+
8
+ class HelpCommand < AbstractCommand
9
+ command_name "help"
10
+ desc "Print help for commands"
11
+
12
+ parameter "[COMMAND] ...", "command"
13
+
14
+ def execute
15
+ ShellMainCommand.run('', command_list << '-h')
16
+ HammerCLI::EX_OK
17
+ end
18
+ end
19
+
20
+ class ExitCommand < AbstractCommand
21
+ command_name "exit"
22
+ desc "Exit interactive shell"
23
+
24
+ def execute
25
+ exit HammerCLI::EX_OK
26
+ end
27
+ end
28
+
29
+ class AuthCommand < AbstractCommand
30
+ command_name "auth"
31
+ desc "Login and logout actions"
32
+
33
+ class LoginCommand < AbstractCommand
34
+ command_name "login"
35
+ desc "Set credentials"
36
+
37
+ def execute
38
+ context[:username] = ask_username
39
+ context[:password] = ask_password
40
+ HammerCLI::EX_OK
41
+ end
42
+ end
43
+
44
+ class LogoutCommand < AbstractCommand
45
+ command_name "logout"
46
+ desc "Wipe your credentials"
47
+
48
+ def execute
49
+ context[:username] = nil
50
+ context[:password] = nil
51
+
52
+ if username(false)
53
+ print_message("Credentials deleted, using defaults now.")
54
+ print_message("You are logged in as [ %s ]." % username(false))
55
+ else
56
+ print_message("Credentials deleted.")
57
+ end
58
+ HammerCLI::EX_OK
59
+ end
60
+ end
61
+
62
+ class InfoCommand < AbstractCommand
63
+ command_name "status"
64
+ desc "Information about current user"
65
+
66
+ def execute
67
+ if username(false)
68
+ print_message("You are logged in as [ %s ]." % username(false))
69
+ else
70
+ print_message("You are currently not logged in.\nUse 'auth login' to set credentials.")
71
+ end
72
+ HammerCLI::EX_OK
73
+ end
74
+ end
75
+
76
+ autoload_subcommands
77
+ end
78
+
79
+
80
+ def self.load_commands(main_cls)
81
+ cmds = main_cls.recognised_subcommands.select do |sub_cmd|
82
+ !(sub_cmd.subcommand_class <= HammerCLI::ShellCommand)
83
+ end
84
+ self.recognised_subcommands.push(*cmds)
85
+ end
86
+
87
+ autoload_subcommands
88
+ end
89
+
6
90
  class ShellCommand < AbstractCommand
7
91
 
8
92
  def execute
9
- Readline.completion_append_character = " "
10
- Readline.completer_word_break_characters = ''
93
+ ShellMainCommand.load_commands(HammerCLI::MainCommand)
94
+
95
+ Readline.completion_append_character = ''
96
+ Readline.completer_word_break_characters = ' '
11
97
  Readline.completion_proc = complete_proc
12
98
 
13
99
  stty_save = `stty -g`.chomp
14
100
 
15
101
  begin
16
- while line = Readline.readline('hammer> ', true)
17
- HammerCLI::MainCommand.run('hammer', line.split) unless line.start_with? 'shell'
102
+ print_welcome_message
103
+ while line = Readline.readline(prompt, true)
104
+ ShellMainCommand.run('', line.split, context) unless line.start_with? 'shell' or line.strip.empty?
18
105
  end
19
106
  rescue Interrupt => e
20
107
  puts
@@ -25,25 +112,27 @@ module HammerCLI
25
112
 
26
113
  private
27
114
 
115
+ def prompt
116
+ 'hammer> '
117
+ end
118
+
119
+ def print_welcome_message
120
+ print_message("Welcome to the hammer interactive shell")
121
+ print_message("Type 'help' for usage information")
122
+ end
123
+
28
124
  def common_prefix(results)
29
125
  results.delete_if{ |r| !r[0].start_with?(results[0][0][0]) }.length == results.length
30
126
  end
31
127
 
32
128
  def complete_proc
33
- Proc.new do |cpl|
34
- res = HammerCLI::MainCommand.autocomplete(cpl.split)
35
- # if there is one result or if results have common prefix
36
- # readline tries to replace current input with results
37
- # thus we should join the results with the start of the line
38
- if res.length == 1 || common_prefix(res)
39
- res.map { |r| r.delete_if{ |e| e == '' }.reverse.join(' ') }
40
- else
41
- res.map{ |e| e[0] }
42
- end
129
+ completer = Completer.new(ShellMainCommand)
130
+ Proc.new do |last_word|
131
+ completer.complete(Readline.line_buffer)
43
132
  end
44
133
  end
45
134
 
46
135
  end
47
136
 
48
- HammerCLI::MainCommand.subcommand "shell", "Interactive Shell", HammerCLI::ShellCommand
137
+ HammerCLI::MainCommand.subcommand "shell", "Interactive shell", HammerCLI::ShellCommand
49
138
  end
@@ -1,5 +1,5 @@
1
1
  module HammerCLI
2
2
  def self.version
3
- @version ||= Gem::Version.new '0.0.12'
3
+ @version ||= Gem::Version.new '0.0.13'
4
4
  end
5
5
  end
data/lib/hammer_cli.rb CHANGED
@@ -5,6 +5,7 @@ require 'hammer_cli/settings'
5
5
  require 'hammer_cli/validator'
6
6
  require 'hammer_cli/output'
7
7
  require 'hammer_cli/options/normalizers'
8
+ require 'hammer_cli/completer'
8
9
  require 'hammer_cli/abstract'
9
10
  require 'hammer_cli/main'
10
11
 
@@ -14,13 +14,14 @@ describe HammerCLI::Apipie::Command do
14
14
  class CommandB < ParentCommand
15
15
  end
16
16
  end
17
-
17
+
18
18
  class CommandC < CommandA
19
19
  end
20
20
 
21
21
 
22
+ let(:ctx) { { :adapter => :silent, :interactive => false } }
22
23
  let(:cmd_class) { HammerCLI::Apipie::Command.dup }
23
- let(:cmd) { cmd_class.new("") }
24
+ let(:cmd) { cmd_class.new("", ctx) }
24
25
 
25
26
  context "setting identifiers" do
26
27
 
@@ -88,7 +89,7 @@ describe HammerCLI::Apipie::Command do
88
89
 
89
90
  it "must raise exception when no attribute is passed" do
90
91
  cmd_class.identifiers :id, :name
91
- proc { cmd.run([]) }.must_raise Clamp::UsageError
92
+ cmd.run([]).must_equal HammerCLI::EX_USAGE
92
93
  end
93
94
 
94
95
  it "must run without error when no identifiers are declared" do
@@ -133,19 +134,19 @@ describe HammerCLI::Apipie::Command do
133
134
  end
134
135
 
135
136
  it "inherits action from a parent class" do
136
- cmd_b = CommandA::CommandB.new("")
137
+ cmd_b = CommandA::CommandB.new("", ctx)
137
138
  cmd_b.action.must_equal :show
138
139
  cmd_b.class.action.must_equal :show
139
140
  end
140
141
 
141
142
  it "looks up resource in the class' modules" do
142
- cmd_b = CommandA::CommandB.new("")
143
+ cmd_b = CommandA::CommandB.new("", ctx)
143
144
  cmd_b.resource.resource_class.must_equal FakeApi::Resources::Architecture
144
145
  cmd_b.class.resource.resource_class.must_equal FakeApi::Resources::Architecture
145
146
  end
146
147
 
147
148
  it "looks up resource in the superclass" do
148
- cmd_c = CommandC.new("")
149
+ cmd_c = CommandC.new("", ctx)
149
150
  cmd_c.resource.resource_class.must_equal FakeApi::Resources::Architecture
150
151
  cmd_c.class.resource.resource_class.must_equal FakeApi::Resources::Architecture
151
152
  end
@@ -5,7 +5,7 @@ require File.join(File.dirname(__FILE__), 'fake_api')
5
5
  describe HammerCLI::Apipie::ReadCommand do
6
6
 
7
7
  let(:cmd_class) { HammerCLI::Apipie::ReadCommand.dup }
8
- let(:cmd) { cmd_class.new("", { :adapter => :silent }) }
8
+ let(:cmd) { cmd_class.new("", { :adapter => :silent, :interactive => false }) }
9
9
  let(:cmd_run) { cmd.run([]) }
10
10
 
11
11
  it "should raise exception when no action is defined" do
@@ -3,7 +3,9 @@ require File.join(File.dirname(__FILE__), 'fake_api')
3
3
 
4
4
  describe HammerCLI::Apipie::WriteCommand do
5
5
 
6
- let(:cmd) { HammerCLI::Apipie::WriteCommand.new("") }
6
+
7
+ let(:ctx) { { :interactive => false } }
8
+ let(:cmd) { HammerCLI::Apipie::WriteCommand.new("", ctx) }
7
9
  let(:cmd_run) { cmd.run([]) }
8
10
 
9
11
  it "should raise exception when no action is defined" do
@@ -0,0 +1,178 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+ require 'tempfile'
3
+
4
+
5
+ describe HammerCLI::CompleterLine do
6
+
7
+ let(:unfinished_line) { "architecture list --name arch" }
8
+ let(:finished_line) { "architecture list --name arch " }
9
+
10
+ context "splitting words" do
11
+
12
+ it "should split basic line" do
13
+ line = HammerCLI::CompleterLine.new(finished_line)
14
+ line.must_equal ["architecture", "list", "--name", "arch"]
15
+ end
16
+
17
+ it "should split basic line with space at the end" do
18
+ line = HammerCLI::CompleterLine.new(finished_line)
19
+ line.must_equal ["architecture", "list", "--name", "arch"]
20
+ end
21
+
22
+ end
23
+
24
+ context "last word finished" do
25
+
26
+ it "should recongize unfinished line" do
27
+ line = HammerCLI::CompleterLine.new(unfinished_line)
28
+ line.finished?.must_equal false
29
+ end
30
+
31
+ it "should recongize finished line" do
32
+ line = HammerCLI::CompleterLine.new(finished_line)
33
+ line.finished?.must_equal true
34
+ end
35
+
36
+ it "should recongize empty line as finished" do
37
+ line = HammerCLI::CompleterLine.new("")
38
+ line.finished?.must_equal true
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+
45
+
46
+ describe HammerCLI::Completer do
47
+
48
+
49
+ class FakeMainCmd < HammerCLI::AbstractCommand
50
+
51
+ class FakeNormalizer < HammerCLI::Options::Normalizers::AbstractNormalizer
52
+
53
+ def format(val)
54
+ val
55
+ end
56
+
57
+ def complete(val)
58
+ ["small ", "tall "]
59
+ end
60
+
61
+ end
62
+
63
+ class AnabolicCmd < HammerCLI::AbstractCommand
64
+ command_name "anabolic"
65
+ end
66
+
67
+ class ApeCmd < HammerCLI::AbstractCommand
68
+ command_name "ape"
69
+
70
+ option "--hairy", :flag, "Description"
71
+ option "--weight", "WEIGHT", "Description",
72
+ :format => FakeNormalizer.new
73
+ option "--height", "HEIGHT", "Description",
74
+ :format => FakeNormalizer.new
75
+
76
+ class MakkakCmd < HammerCLI::AbstractCommand
77
+ command_name "makkak"
78
+ end
79
+
80
+ class MalpaCmd < HammerCLI::AbstractCommand
81
+ command_name "malpa"
82
+ end
83
+
84
+ class OrangutanCmd < HammerCLI::AbstractCommand
85
+ command_name "orangutan"
86
+ end
87
+
88
+ autoload_subcommands
89
+ end
90
+
91
+ class ApocalypseCmd < HammerCLI::AbstractCommand
92
+ command_name "apocalypse"
93
+ end
94
+
95
+ class BeastCmd < HammerCLI::AbstractCommand
96
+ command_name "beast"
97
+ end
98
+
99
+ autoload_subcommands
100
+ end
101
+
102
+
103
+ let(:completer) { HammerCLI::Completer.new(FakeMainCmd) }
104
+
105
+ context "command completion" do
106
+ it "should offer all available commands" do
107
+ completer.complete("").sort.must_equal ["anabolic ", "ape ", "apocalypse ", "beast ", "-h ", "--help "].sort
108
+ end
109
+
110
+ it "should offer nothing when the line does not match" do
111
+ completer.complete("x").must_equal []
112
+ end
113
+
114
+ it "should filter by first letter" do
115
+ completer.complete("a").sort.must_equal ["anabolic ", "ape ", "apocalypse "].sort
116
+ end
117
+
118
+ it "should filter by first two letters" do
119
+ completer.complete("ap").sort.must_equal ["ape ", "apocalypse "].sort
120
+ end
121
+
122
+ it "should offer all available subcommands and options" do
123
+ completer.complete("ape ").sort.must_equal ["makkak ", "malpa ", "orangutan ", "--hairy ", "--weight ", "--height ", "-h ", "--help "].sort
124
+ end
125
+
126
+ it "should offer all available subcommands and options even if a flag has been passed" do
127
+ completer.complete("ape --hairy ").sort.must_equal ["makkak ", "malpa ", "orangutan ", "--hairy ", "--weight ", "--height ", "-h ", "--help "].sort
128
+ end
129
+
130
+ it "should offer all available subcommands and options even if an option has been passed" do
131
+ completer.complete("ape --weight 12kg ").sort.must_equal ["makkak ", "malpa ", "orangutan ", "--hairy ", "--weight ", "--height ", "-h ", "--help "].sort
132
+ end
133
+
134
+ it "should offer all available subcommands and options even if an egual sign option has been passed" do
135
+ completer.complete("ape --weight=12kg ").sort.must_equal ["makkak ", "malpa ", "orangutan ", "--hairy ", "--weight ", "--height ", "-h ", "--help "].sort
136
+ end
137
+ end
138
+
139
+
140
+ context "option value completion" do
141
+ it "should complete option values" do
142
+ completer.complete("ape --height ").sort.must_equal ["small ", "tall "].sort
143
+ end
144
+
145
+ it "should complete option values" do
146
+ completer.complete("ape --height s").must_equal ["small "]
147
+ end
148
+ end
149
+
150
+
151
+ context "subcommand completion" do
152
+ it "should filter subcommands by first letter" do
153
+ completer.complete("ape m").sort.must_equal ["makkak ", "malpa "].sort
154
+ end
155
+
156
+ it "should offer nothing when the line does not match any subcommand" do
157
+ completer.complete("ape x").must_equal []
158
+ end
159
+
160
+ it "should ignore flags specified before the last command" do
161
+ completer.complete("ape --hairy m").sort.must_equal ["makkak ", "malpa "].sort
162
+ end
163
+
164
+ it "should ignore options specified before the last command" do
165
+ completer.complete("ape --weight 12kg m").sort.must_equal ["makkak ", "malpa "].sort
166
+ end
167
+
168
+ it "should ignore equal sign separated options specified before the last command" do
169
+ completer.complete("ape --weight=12kg m").sort.must_equal ["makkak ", "malpa "].sort
170
+ end
171
+
172
+ it "should filter subcommands by first three letters" do
173
+ completer.complete("ape mak").must_equal ["makkak "]
174
+ end
175
+ end
176
+
177
+ end
178
+
@@ -11,6 +11,7 @@ describe HammerCLI::ExceptionHandler do
11
11
  let(:output) { HammerCLI::Output::Output.new }
12
12
  let(:handler) { HammerCLI::ExceptionHandler.new(:output => output)}
13
13
  let(:heading) { "Something went wrong" }
14
+ let(:cmd) { Class.new(HammerCLI::AbstractCommand).new("command_name") }
14
15
 
15
16
  it "should handle unauthorized" do
16
17
  output.expects(:print_error).with(heading, "Invalid username or password")
@@ -28,6 +29,18 @@ describe HammerCLI::ExceptionHandler do
28
29
  handler.handle_exception(MyException.new('message'), :heading => heading)
29
30
  end
30
31
 
32
+ it "should handle help request" do
33
+ output.expects(:print_message).with(cmd.help)
34
+ handler.handle_exception(Clamp::HelpWanted.new(cmd), :heading => heading)
35
+
36
+ end
37
+
38
+ it "should handle usage error" do
39
+ output.expects(:print_error).with(heading, "Error: wrong_usage\n\nSee: 'command_name --help'")
40
+ handler.handle_exception(Clamp::UsageError.new('wrong_usage', cmd), :heading => heading)
41
+
42
+ end
43
+
31
44
  it "should handle resource not found" do
32
45
  ex = RestClient::ResourceNotFound.new
33
46
  output.expects(:print_error).with(heading, ex.message)
@@ -14,6 +14,8 @@ describe HammerCLI::Options::OptionDefinition do
14
14
  option "--test-format", "TEST_FORMAT", "Test option with a formatter",
15
15
  :format => FakeFormatter.new,
16
16
  :default => "A"
17
+ option "--test-context", "CONTEXT", "Option saved into context",
18
+ :context_target => :test_option
17
19
  end
18
20
 
19
21
  describe "formatters" do
@@ -30,7 +32,7 @@ describe HammerCLI::Options::OptionDefinition do
30
32
 
31
33
  opt_instance = opt.of(TestOptionFormattersCmd.new([]))
32
34
  # clamp api changed in 0.6.2
33
- if opt_instance.respond_to? :write
35
+ if opt_instance.respond_to? :write
34
36
  opt_instance.write('B')
35
37
  else
36
38
  opt_instance.take('B')
@@ -39,5 +41,13 @@ describe HammerCLI::Options::OptionDefinition do
39
41
  end
40
42
  end
41
43
 
44
+ describe "context" do
45
+ it "should save option to context" do
46
+ context = {}
47
+ cmd = TestOptionFormattersCmd.new("", context)
48
+ cmd.run(["--test-context=VALUE"])
49
+ context[:test_option].must_equal "VALUE"
50
+ end
51
+ end
42
52
  end
43
53
 
@@ -53,6 +53,25 @@ describe HammerCLI::Output::Adapter::Table do
53
53
  out.must_match(/.*-DOT-.*/)
54
54
  end
55
55
  end
56
+
57
+ context "sort_columns" do
58
+ let(:field_firstname) { Fields::DataField.new(:path => [:firstname], :label => "Firstname") }
59
+ let(:field_lastname) { Fields::DataField.new(:path => [:lastname], :label => "Lastname") }
60
+ let(:fields) {
61
+ [field_firstname, field_lastname]
62
+ }
63
+ let(:data) { HammerCLI::Output::RecordCollection.new [{
64
+ :firstname => "John",
65
+ :lastname => "Doe"
66
+ }]}
67
+
68
+ it "should sort output" do
69
+ TablePrint::Printer.any_instance.stubs(:table_print).returns(
70
+ "LASTNAME | FIRSTNAME\n---------|----------\nDoe | John \n")
71
+ proc { adapter.print_collection(fields, data) }.must_output(
72
+ "----------|---------\nFIRSTNAME | LASTNAME\n----------|---------\nJohn | Doe \n----------|---------\n")
73
+ end
74
+ end
56
75
  end
57
76
 
58
77
  end
@@ -5,7 +5,7 @@ describe HammerCLI::Output::Output do
5
5
  let(:adapter) { HammerCLI::Output::Adapter::Silent }
6
6
  let(:definition) { HammerCLI::Output::Definition.new }
7
7
 
8
- let(:context) { { :adapter => :silent } }
8
+ let(:context) { { :adapter => :silent, :interactive => false } }
9
9
  let(:out_class) { HammerCLI::Output::Output }
10
10
  let(:out) { out_class.new(context) }
11
11