hammer_cli 0.0.12 → 0.0.13

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