rhcp_shell 0.0.1
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.
- data/History.txt +6 -0
- data/Manifest.txt +12 -0
- data/README +3 -0
- data/README.txt +48 -0
- data/Rakefile +71 -0
- data/lib/base_shell.rb +100 -0
- data/lib/rhcp_shell.rb +108 -0
- data/lib/rhcp_shell_backend.rb +513 -0
- data/lib/shell_backend.rb +29 -0
- data/start_shell.sh +3 -0
- data/test/rhcp_shell_backend_test.rb +236 -0
- data/test/setup_test_registry.rb +109 -0
- metadata +77 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README
ADDED
data/README.txt
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
= sow_test
|
2
|
+
|
3
|
+
* FIX (url)
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
FIX (describe your package)
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
* FIX (list of features or problems)
|
12
|
+
|
13
|
+
== SYNOPSIS:
|
14
|
+
|
15
|
+
FIX (code sample of usage)
|
16
|
+
|
17
|
+
== REQUIREMENTS:
|
18
|
+
|
19
|
+
* FIX (list of requirements)
|
20
|
+
|
21
|
+
== INSTALL:
|
22
|
+
|
23
|
+
* FIX (sudo gem install, anything else)
|
24
|
+
|
25
|
+
== LICENSE:
|
26
|
+
|
27
|
+
(The MIT License)
|
28
|
+
|
29
|
+
Copyright (c) 2009 FIX
|
30
|
+
|
31
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
32
|
+
a copy of this software and associated documentation files (the
|
33
|
+
'Software'), to deal in the Software without restriction, including
|
34
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
35
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
36
|
+
permit persons to whom the Software is furnished to do so, subject to
|
37
|
+
the following conditions:
|
38
|
+
|
39
|
+
The above copyright notice and this permission notice shall be
|
40
|
+
included in all copies or substantial portions of the Software.
|
41
|
+
|
42
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
43
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
44
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
45
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
46
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
47
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
48
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake/packagetask'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
|
7
|
+
PKG_NAME = 'rhcp_shell'
|
8
|
+
PKG_VERSION = '0.0.1'
|
9
|
+
|
10
|
+
desc "Default Task"
|
11
|
+
task :default => [ :test ]
|
12
|
+
|
13
|
+
###############################################
|
14
|
+
### TESTS
|
15
|
+
Rake::TestTask.new() { |t|
|
16
|
+
t.libs << "test"
|
17
|
+
t.test_files = FileList['test/*_test.rb']
|
18
|
+
t.verbose = true
|
19
|
+
}
|
20
|
+
|
21
|
+
###############################################
|
22
|
+
### RDOC
|
23
|
+
Rake::RDocTask.new { |rdoc|
|
24
|
+
rdoc.rdoc_dir = 'doc'
|
25
|
+
rdoc.title = "RHCP Shell"
|
26
|
+
rdoc.options << '--line-numbers' << '--inline-source' <<
|
27
|
+
'--accessor' << 'cattr_accessor=object'
|
28
|
+
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
|
29
|
+
#rdoc.rdoc_files.include('README', 'CHANGELOG')
|
30
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
31
|
+
}
|
32
|
+
|
33
|
+
###############################################
|
34
|
+
### METRICS
|
35
|
+
task :lines do
|
36
|
+
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
|
37
|
+
|
38
|
+
for file_name in FileList["lib/**/*.rb"]
|
39
|
+
f = File.open(file_name)
|
40
|
+
|
41
|
+
while line = f.gets
|
42
|
+
lines += 1
|
43
|
+
next if line =~ /^\s*$/
|
44
|
+
next if line =~ /^\s*#/
|
45
|
+
codelines += 1
|
46
|
+
end
|
47
|
+
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
|
48
|
+
|
49
|
+
total_lines += lines
|
50
|
+
total_codelines += codelines
|
51
|
+
|
52
|
+
lines, codelines = 0, 0
|
53
|
+
end
|
54
|
+
|
55
|
+
puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
|
56
|
+
end
|
57
|
+
|
58
|
+
#rcov -I lib/ -I test/ -x rcov.rb -x var/lib -x lib/shell_backend.rb test/*test.rb
|
59
|
+
|
60
|
+
task :update_manifest do
|
61
|
+
system "rake check_manifest 2>/dev/null | grep -vE 'qooxdoo|nbproject|coverage' | grep -E '^\+' | grep -vE '^$' | grep -v '(in ' | grep -vE '^\+\+\+' | cut -b 2-200 | patch"
|
62
|
+
end
|
63
|
+
|
64
|
+
require 'rubygems'
|
65
|
+
require 'hoe'
|
66
|
+
|
67
|
+
Hoe.new('rhcp_shell', PKG_VERSION) do |p|
|
68
|
+
p.rubyforge_name = 'rhcp' # if different than lowercase project name
|
69
|
+
p.developer('Philipp T.', 'philipp@hitchhackers.net')
|
70
|
+
end
|
71
|
+
|
data/lib/base_shell.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# This class is an abstract implementation of a command shell
|
2
|
+
# It handles command completion and history functions.
|
3
|
+
# For the actual business logic, you need to pass it an implementation of ShellBackend
|
4
|
+
class BaseShell
|
5
|
+
attr_reader :backend
|
6
|
+
|
7
|
+
def initialize(backend)
|
8
|
+
@logger = $logger
|
9
|
+
@backend = backend
|
10
|
+
|
11
|
+
at_exit { console.close }
|
12
|
+
|
13
|
+
trap("INT") {
|
14
|
+
Thread.kill(@thread)
|
15
|
+
@backend.process_ctrl_c
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
class SimpleConsole
|
20
|
+
def initialize(input = $stdin)
|
21
|
+
@input = input
|
22
|
+
end
|
23
|
+
|
24
|
+
def readline
|
25
|
+
begin
|
26
|
+
line = @input.readline
|
27
|
+
line.chomp! if line
|
28
|
+
line
|
29
|
+
rescue EOFError
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def close
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class ReadlineConsole
|
39
|
+
HISTORY_FILE = ".jscmd_history"
|
40
|
+
MAX_HISTORY = 200
|
41
|
+
|
42
|
+
def history_path
|
43
|
+
File.join(ENV['HOME'] || ENV['USERPROFILE'], HISTORY_FILE)
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(shell)
|
47
|
+
@shell = shell
|
48
|
+
if File.exist?(history_path)
|
49
|
+
hist = File.readlines(history_path).map{|line| line.chomp}
|
50
|
+
Readline::HISTORY.push(*hist)
|
51
|
+
end
|
52
|
+
if Readline.methods.include?("basic_word_break_characters=")
|
53
|
+
#Readline.basic_word_break_characters = " \t\n\\`@><=;|&{([+-*/%"
|
54
|
+
Readline.basic_word_break_characters = " \t\n\\`@><=;|&{([+*%"
|
55
|
+
end
|
56
|
+
Readline.completion_append_character = nil
|
57
|
+
Readline.completion_proc = @shell.backend.method(:complete).to_proc
|
58
|
+
end
|
59
|
+
|
60
|
+
def close
|
61
|
+
open(history_path, "wb") do |f|
|
62
|
+
history = Readline::HISTORY.to_a
|
63
|
+
if history.size > MAX_HISTORY
|
64
|
+
history = history[history.size - MAX_HISTORY, MAX_HISTORY]
|
65
|
+
end
|
66
|
+
history.each{|line| f.puts(line)}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def readline
|
71
|
+
line = Readline.readline(@shell.backend.prompt, true)
|
72
|
+
Readline::HISTORY.pop if /^\s*$/ =~ line
|
73
|
+
line
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def console
|
78
|
+
@console ||= $stdin.tty? ? ReadlineConsole.new(self) : SimpleConsole.new
|
79
|
+
end
|
80
|
+
|
81
|
+
def run
|
82
|
+
backend.show_banner
|
83
|
+
loop do
|
84
|
+
@thread = Thread.new {
|
85
|
+
break unless line = console.readline
|
86
|
+
|
87
|
+
if line then
|
88
|
+
@logger.debug "got : #{line}"
|
89
|
+
else
|
90
|
+
@logger.debug "got an empty line"
|
91
|
+
end
|
92
|
+
|
93
|
+
backend.process_input line
|
94
|
+
}
|
95
|
+
@thread.join
|
96
|
+
end
|
97
|
+
$stderr.puts "Exiting shell..."
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
data/lib/rhcp_shell.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "readline"
|
4
|
+
require "logger"
|
5
|
+
require "getoptlong"
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
|
9
|
+
require 'rhcp'
|
10
|
+
|
11
|
+
require 'base_shell'
|
12
|
+
require 'rhcp_shell_backend'
|
13
|
+
|
14
|
+
# TODO version number
|
15
|
+
SHELL_VERSION = "0.1"
|
16
|
+
|
17
|
+
class RhcpShell
|
18
|
+
|
19
|
+
HELP_STRING = <<EOF
|
20
|
+
|
21
|
+
RHCP Command Shell v #{SHELL_VERSION} (using RHCP library v #{RHCP::Version.to_s})
|
22
|
+
|
23
|
+
Usage:
|
24
|
+
rhcp_shell.rb [--hostname=<hostname>] [--username=<username> --password=<password>]
|
25
|
+
[--help]
|
26
|
+
|
27
|
+
Options:
|
28
|
+
--hostname=<hostname>
|
29
|
+
the URL to the RHCP server you want to connect against, e.g.
|
30
|
+
http://server.local.network/rhcp
|
31
|
+
If the specified hostname does not start with "http", it is automatically expanded to
|
32
|
+
http://<hostname>:42000/rhcp
|
33
|
+
You can optionally specify a port number to connect against like this:
|
34
|
+
http://myserver:42000
|
35
|
+
If you do not specify a hostname, the shell will try to connect against
|
36
|
+
http://localhost:42000
|
37
|
+
|
38
|
+
--username/--password
|
39
|
+
the authentication data you want to use for connecting to the RHCP server
|
40
|
+
|
41
|
+
--help
|
42
|
+
displays this help screen.
|
43
|
+
|
44
|
+
EOF
|
45
|
+
|
46
|
+
def run
|
47
|
+
opts = GetoptLong.new(
|
48
|
+
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
|
49
|
+
[ '--username', '-u', GetoptLong::REQUIRED_ARGUMENT ],
|
50
|
+
[ '--password', '-p', GetoptLong::REQUIRED_ARGUMENT ],
|
51
|
+
[ '--hostname', GetoptLong::REQUIRED_ARGUMENT ]
|
52
|
+
)
|
53
|
+
|
54
|
+
options = Hash.new
|
55
|
+
opts.each do |opt, arg|
|
56
|
+
case opt
|
57
|
+
when '--help'
|
58
|
+
puts HELP_STRING
|
59
|
+
Kernel.exit(1)
|
60
|
+
else
|
61
|
+
opt =~ /--(.+)/
|
62
|
+
$logger.debug "setting #{arg} for #{$1}" unless $1 == "password"
|
63
|
+
options[$1] = arg
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
host = options["hostname"]
|
68
|
+
if host == nil then
|
69
|
+
host = "http://localhost:42000"
|
70
|
+
else
|
71
|
+
if host !~ /http:/ then
|
72
|
+
host = "http://#{host}:42000/rhcp"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
$logger.debug "now connecting to #{host}"
|
76
|
+
|
77
|
+
# TODO add interactive query for password!
|
78
|
+
|
79
|
+
begin
|
80
|
+
url = URI.parse(host)
|
81
|
+
@http_broker = RHCP::Client::HttpBroker.new(url)
|
82
|
+
|
83
|
+
backend = RHCPShellBackend.new(@http_broker)
|
84
|
+
backend.banner = <<EOF
|
85
|
+
Good morning, this is the generic RHCP Shell.
|
86
|
+
Press <tab> for command completion or type "help" for a list of commands.
|
87
|
+
If you want to exit this shell, please press Ctrl+C or type "exit".
|
88
|
+
|
89
|
+
EOF
|
90
|
+
$logger.debug "backend has been instantiated : #{backend}"
|
91
|
+
|
92
|
+
shell = BaseShell.new(backend)
|
93
|
+
shell.run
|
94
|
+
rescue => ex
|
95
|
+
puts "There occurred an HTTP error while connecting to the RHCP: #{ex}"
|
96
|
+
puts "Please connect against another server or fix the connection problem."
|
97
|
+
puts ex.backtrace.join("\n")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
# TODO introduce something like the RAILS_ENVs
|
104
|
+
$logger = Logger.new("rhcp_shell.log")
|
105
|
+
RHCP::ModuleHelper.instance().logger = $logger
|
106
|
+
|
107
|
+
shell = RhcpShell.new
|
108
|
+
shell.run
|
@@ -0,0 +1,513 @@
|
|
1
|
+
require 'shell_backend.rb'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rhcp'
|
5
|
+
|
6
|
+
# This shell presents RHCP commands to the user and handles all the parameter
|
7
|
+
# lookup, validation and command completion stuff
|
8
|
+
#
|
9
|
+
# It uses a RHCP registry/broker as a data backend and possibly for communication with
|
10
|
+
# a server.
|
11
|
+
#
|
12
|
+
# This shell implementation handles two modes - one for entering/selecting
|
13
|
+
# a command, another for entering/selecting parameter values.
|
14
|
+
# If a command and all mandatory parameters are entered/selected, the command is
|
15
|
+
# executed.
|
16
|
+
class RHCPShellBackend < ShellBackend
|
17
|
+
|
18
|
+
attr_reader :prompt
|
19
|
+
attr_accessor :banner
|
20
|
+
|
21
|
+
def initialize(command_broker)
|
22
|
+
super()
|
23
|
+
|
24
|
+
local_broker = setup_local_broker
|
25
|
+
@command_broker = RHCP::DispatchingBroker.new()
|
26
|
+
@command_broker.add_broker(command_broker)
|
27
|
+
@command_broker.add_broker(local_broker)
|
28
|
+
|
29
|
+
reset_to_command_mode
|
30
|
+
end
|
31
|
+
|
32
|
+
def setup_local_broker
|
33
|
+
broker = RHCP::Broker.new()
|
34
|
+
begin
|
35
|
+
broker.register_command RHCP::Command.new("exit", "closes the shell",
|
36
|
+
lambda { |req,res|
|
37
|
+
puts "Have a nice day"
|
38
|
+
Kernel.exit(0)
|
39
|
+
}
|
40
|
+
)
|
41
|
+
|
42
|
+
def param_example(param, suffix = "")
|
43
|
+
result = ""
|
44
|
+
result += "<" if param.is_default_param
|
45
|
+
|
46
|
+
if param.is_default_param then
|
47
|
+
result += "#{param.name}#{suffix}"
|
48
|
+
else
|
49
|
+
result += "#{param.name}"
|
50
|
+
result += "=<value#{suffix}>"
|
51
|
+
end
|
52
|
+
|
53
|
+
result += ">" if param.is_default_param
|
54
|
+
result
|
55
|
+
end
|
56
|
+
|
57
|
+
command = RHCP::Command.new("help", "displays help about this shell",
|
58
|
+
lambda {
|
59
|
+
|req,res|
|
60
|
+
|
61
|
+
if (req.has_param_value("command"))
|
62
|
+
command_name = req.get_param_value("command")
|
63
|
+
puts "Syntax:"
|
64
|
+
command_line = " #{command_name}"
|
65
|
+
command = @command_broker.get_command(command_name)
|
66
|
+
|
67
|
+
command.params.values.sort { |a,b| a.name <=> b.name }.each do |param|
|
68
|
+
command_line += " "
|
69
|
+
command_line += "[" unless param.mandatory
|
70
|
+
|
71
|
+
command_line += param_example(param)
|
72
|
+
|
73
|
+
if param.allows_multiple_values then
|
74
|
+
command_line += ", "
|
75
|
+
command_line += param_example(param, "2")
|
76
|
+
command_line += ", ..."
|
77
|
+
end
|
78
|
+
|
79
|
+
command_line += "]" unless param.mandatory
|
80
|
+
end
|
81
|
+
puts command_line
|
82
|
+
puts "Description:"
|
83
|
+
puts " #{command.description}"
|
84
|
+
if command.params.size > 0 then
|
85
|
+
puts "Parameters:"
|
86
|
+
command.params.values.sort { |a,b| a.name <=> b.name }.each do |param|
|
87
|
+
puts sprintf(" %-20s %s\n", param.name, param.description)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
puts ""
|
91
|
+
else
|
92
|
+
puts "The following commands are available:"
|
93
|
+
@command_broker.get_command_list.values.sort { |a,b| a.name <=> b.name }.each do |command|
|
94
|
+
# TODO calculate the maximum command name length dynamically
|
95
|
+
puts sprintf(" %-40s %s\n", command.name, command.description)
|
96
|
+
end
|
97
|
+
puts ""
|
98
|
+
puts "Type help <command name> for detailed information about a command."
|
99
|
+
end
|
100
|
+
}
|
101
|
+
).add_param(RHCP::CommandParam.new("command", "the name of the command to display help for",
|
102
|
+
{
|
103
|
+
:mandatory => false,
|
104
|
+
:is_default_param => true,
|
105
|
+
:lookup_method => lambda {
|
106
|
+
@command_broker.get_command_list.values.map { |c| c.name }
|
107
|
+
}
|
108
|
+
}
|
109
|
+
)
|
110
|
+
)
|
111
|
+
command.result_hints[:display_type] = "hidden"
|
112
|
+
broker.register_command command
|
113
|
+
|
114
|
+
command = RHCP::Command.new("detail", "shows details about a single record of the last response (makes sense if you executed a command that returned a table)",
|
115
|
+
lambda { |req,res|
|
116
|
+
if @last_response == nil
|
117
|
+
puts "did not find any old response data...is it possible that you did not execute a command yet that returned a table?"
|
118
|
+
return
|
119
|
+
end
|
120
|
+
row_count = @last_response.data.length
|
121
|
+
begin
|
122
|
+
row_index = req.get_param_value("row_index").to_i
|
123
|
+
raise "invalid index" if (row_index < 1 || row_index > row_count)
|
124
|
+
rescue
|
125
|
+
puts "invalid row index - please specify a number between 1 and #{row_count}"
|
126
|
+
return
|
127
|
+
end
|
128
|
+
puts "displaying details about row \# #{row_index}"
|
129
|
+
@last_response.data[row_index - 1].each do |k,v|
|
130
|
+
puts " #{k}\t#{v}"
|
131
|
+
end
|
132
|
+
|
133
|
+
}
|
134
|
+
).add_param(RHCP::CommandParam.new("row_index", "the index of the row you want to see details about",
|
135
|
+
{
|
136
|
+
:is_default_param => true,
|
137
|
+
:mandatory => true
|
138
|
+
}
|
139
|
+
)
|
140
|
+
)
|
141
|
+
command.result_hints[:display_type] = "hidden"
|
142
|
+
broker.register_command command
|
143
|
+
|
144
|
+
rescue RHCP::RhcpException => ex
|
145
|
+
# TODO do we really want to catch this here?
|
146
|
+
raise ex unless /duplicate command name/ =~ ex.to_s
|
147
|
+
end
|
148
|
+
broker
|
149
|
+
end
|
150
|
+
|
151
|
+
def reset_to_command_mode
|
152
|
+
set_prompt nil
|
153
|
+
|
154
|
+
# this shell has two modes that determine the available tab completion proposals
|
155
|
+
# command_mode
|
156
|
+
# we're waiting for the user to pick a command that should be executed
|
157
|
+
# parameter mode
|
158
|
+
# the command to execute has already been selected, but the user needs to specify additional parameters
|
159
|
+
# we'll start in the mode where no command has been selected yet
|
160
|
+
@command_selected = nil
|
161
|
+
|
162
|
+
# if the user selected a command already, we'll have to collect parameters for this command until
|
163
|
+
# we've got all mandatory parameters so that we can execute the command
|
164
|
+
@collected_params = Hash.new
|
165
|
+
|
166
|
+
# the mandatory params that are still missing (valid in parameter mode only)
|
167
|
+
@missing_params = Array.new
|
168
|
+
|
169
|
+
# the parameter that we're asking for right now
|
170
|
+
@current_param = nil
|
171
|
+
end
|
172
|
+
|
173
|
+
def execute_command_if_possible
|
174
|
+
# check if we got all mandatory params now
|
175
|
+
mandatory_params = @command_selected.params.select { |name,param| param.mandatory }.map { |k,v| v }
|
176
|
+
@missing_params = mandatory_params.select { |p| ! @collected_params.include? p.name }
|
177
|
+
|
178
|
+
if (@missing_params.size > 0) then
|
179
|
+
$logger.debug "got #{@missing_params.size} missing params : #{@missing_params.map{|param| param.name}}"
|
180
|
+
@current_param = @missing_params[0]
|
181
|
+
set_prompt "#{@command_selected.name}.#{@current_param.name}"
|
182
|
+
else
|
183
|
+
execute_command
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
def pre_process_param_value(new_value)
|
189
|
+
$logger.debug "resolving wildcards for param '#{@current_param.name}'"
|
190
|
+
|
191
|
+
# we can only resolve wildcards if we have lookup values
|
192
|
+
if (@current_param.has_lookup_values)
|
193
|
+
# TODO this is only necessary if we've got multiple values, right?
|
194
|
+
# TODO maybe we want to check if 'new_value' holds suspicious characters that necessitate wildcard resolution?
|
195
|
+
# TODO is the range handling possible only with lookup values?
|
196
|
+
# convert "*" into regexp notation ".*"
|
197
|
+
regex_str = new_value.gsub(/\*/, '.*')
|
198
|
+
|
199
|
+
# handle ranges (x..y)
|
200
|
+
result = /(.+?)(\d+)(\.{2})(\d+)(.*)/.match(regex_str)
|
201
|
+
ranged_regex = nil
|
202
|
+
if result then
|
203
|
+
$logger.debug "captures : #{result.captures.map { |v| " #{v} "}}"
|
204
|
+
result.captures[1].upto(result.captures[3]) do |loop|
|
205
|
+
regex_for_this_number = "#{result.captures[0]}#{loop}#{result.captures[4]}"
|
206
|
+
$logger.debug "regex for #{loop} : #{regex_for_this_number}"
|
207
|
+
if ranged_regex == nil then
|
208
|
+
ranged_regex = Regexp.new(regex_for_this_number)
|
209
|
+
else
|
210
|
+
ranged_regex = Regexp.union(ranged_regex, Regexp.new(regex_for_this_number))
|
211
|
+
end
|
212
|
+
end
|
213
|
+
else
|
214
|
+
ranged_regex = Regexp.new(regex_str)
|
215
|
+
end
|
216
|
+
|
217
|
+
$logger.debug "wildcard regexp : #{ranged_regex}"
|
218
|
+
|
219
|
+
re = ranged_regex
|
220
|
+
|
221
|
+
# get lookup values, filter and return them
|
222
|
+
lookup_values = @current_param.get_lookup_values()
|
223
|
+
lookup_values.select { |lookup_value| re.match(lookup_value) }
|
224
|
+
else
|
225
|
+
[ new_value ]
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# checks param values for validity and adds them to our value collection
|
230
|
+
# expands wildcard parameters if appropriate
|
231
|
+
# returns the values that have been added (might be more than 'new_value' when wildcards are used)
|
232
|
+
def add_parameter_value(new_value)
|
233
|
+
# pre-process the value if necessary
|
234
|
+
processed_param_values = pre_process_param_value(new_value)
|
235
|
+
# TODO this check is already part of check_param_is_valid (which is called later in this method and when the request is created) - we do not want to check this three times...?
|
236
|
+
if processed_param_values.size == 0
|
237
|
+
raise RHCP::RhcpException.new("invalid value '#{new_value}' for parameter '#{@current_param.name}'")
|
238
|
+
end
|
239
|
+
#processed_param_values = [ new_value ]
|
240
|
+
processed_param_values.each do |value|
|
241
|
+
# TODO we need to pass a value array here, and we should include the already selected param values
|
242
|
+
@current_param.check_param_is_valid([ value ])
|
243
|
+
$logger.debug "accepted value #{value} for param #{@current_param.name}"
|
244
|
+
|
245
|
+
@collected_params[@current_param.name] = Array.new if @collected_params[@current_param.name] == nil
|
246
|
+
@collected_params[@current_param.name] << value
|
247
|
+
end
|
248
|
+
processed_param_values
|
249
|
+
end
|
250
|
+
|
251
|
+
def print_cell(col_name, the_value)
|
252
|
+
result = "| "
|
253
|
+
result += the_value.to_s
|
254
|
+
1.upto(@max_width[col_name] - the_value.to_s.length) { |i|
|
255
|
+
result += " "
|
256
|
+
}
|
257
|
+
result += " "
|
258
|
+
result
|
259
|
+
end
|
260
|
+
|
261
|
+
def print_line
|
262
|
+
result = ""
|
263
|
+
@total_width.times { |i| result += "-" }
|
264
|
+
result += "\n"
|
265
|
+
result
|
266
|
+
end
|
267
|
+
|
268
|
+
def execute_command
|
269
|
+
begin
|
270
|
+
command = @command_broker.get_command(@command_selected.name)
|
271
|
+
request = RHCP::Request.new(command, @collected_params)
|
272
|
+
response = command.execute_request(request)
|
273
|
+
if (response.status == RHCP::Response::Status::OK)
|
274
|
+
$logger.debug "raw result : #{response.data}"
|
275
|
+
$logger.debug "display_type : #{command.result_hints[:display_type]}"
|
276
|
+
if command.result_hints[:display_type] == "table"
|
277
|
+
@last_response = response # we might want to access this response in further commands
|
278
|
+
# TODO make sure that the response really holds the correct data types and that we've got at least one column
|
279
|
+
# TODO check that all columns in overview_columns are valid
|
280
|
+
# TODO check that all columns in column_titles are valid and match overview_columns
|
281
|
+
output = ""
|
282
|
+
|
283
|
+
# let's find out which columns we want to display
|
284
|
+
$logger.debug "overview columns : #{command.result_hints[:overview_columns]}"
|
285
|
+
columns_to_display = command.result_hints.has_key?(:overview_columns) ?
|
286
|
+
command.result_hints[:overview_columns].clone() :
|
287
|
+
# by default, we'll display all columns, sorted alphabetically
|
288
|
+
columns_to_display = response.data[0].keys.sort
|
289
|
+
|
290
|
+
# and which titles they should have (default : column names)
|
291
|
+
column_title_list = command.result_hints.has_key?(:column_titles) ?
|
292
|
+
command.result_hints[:column_titles].clone() :
|
293
|
+
column_title_list = columns_to_display
|
294
|
+
|
295
|
+
# TODO the sorting column should be configurable
|
296
|
+
first_column = columns_to_display[0]
|
297
|
+
$logger.debug "sorting by #{first_column}"
|
298
|
+
response.data = response.data.sort { |a,b| a[first_column] <=> b[first_column] }
|
299
|
+
|
300
|
+
# add the index column
|
301
|
+
columns_to_display.unshift "__idx"
|
302
|
+
column_title_list.unshift "\#"
|
303
|
+
count = 1
|
304
|
+
response.data.each do |row|
|
305
|
+
row["__idx"] = count
|
306
|
+
count = count+1
|
307
|
+
end
|
308
|
+
|
309
|
+
|
310
|
+
column_titles = {}
|
311
|
+
0.upto(column_title_list.length - 1) do |i|
|
312
|
+
column_titles[columns_to_display[i]] = column_title_list[i]
|
313
|
+
end
|
314
|
+
$logger.debug "column title : #{column_titles}"
|
315
|
+
|
316
|
+
# find the maximum column width for each column
|
317
|
+
@max_width = {}
|
318
|
+
response.data.each do |row|
|
319
|
+
row.each do |k,v|
|
320
|
+
if ! @max_width.has_key?(k) || v.to_s.length > @max_width[k]
|
321
|
+
@max_width[k] = v.to_s.length
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# check the column_title
|
327
|
+
columns_to_display.each do |col_name|
|
328
|
+
if column_titles[col_name].length > @max_width[col_name]
|
329
|
+
@max_width[col_name] = column_titles[col_name].length
|
330
|
+
end
|
331
|
+
end
|
332
|
+
#@max_width["row_count"] = response.data.length.to_s.length
|
333
|
+
$logger.debug "max width : #{@max_width}"
|
334
|
+
|
335
|
+
# and build headers
|
336
|
+
@total_width = 2 + columns_to_display.length-1 # separators at front and end of table and between the values
|
337
|
+
columns_to_display.each do |col|
|
338
|
+
@total_width += @max_width[col] + 2 # each column has a space in front and behind the value
|
339
|
+
end
|
340
|
+
output += print_line
|
341
|
+
|
342
|
+
columns_to_display.each do |col|
|
343
|
+
output += print_cell(col, column_titles[col])
|
344
|
+
end
|
345
|
+
output += "|\n"
|
346
|
+
|
347
|
+
output += print_line
|
348
|
+
|
349
|
+
# print the table values
|
350
|
+
response.data.each do |row|
|
351
|
+
columns_to_display.each do |col|
|
352
|
+
output += print_cell(col, row[col])
|
353
|
+
end
|
354
|
+
output += "|\n"
|
355
|
+
end
|
356
|
+
output += print_line
|
357
|
+
|
358
|
+
puts output
|
359
|
+
elsif command.result_hints[:display_type] == "list"
|
360
|
+
output = ""
|
361
|
+
response.data.each do |row|
|
362
|
+
output += "#{row}\n"
|
363
|
+
end
|
364
|
+
puts output
|
365
|
+
elsif command.result_hints[:display_type] == "hidden"
|
366
|
+
$logger.debug "suppressing output due to display_type 'hidden'"
|
367
|
+
else
|
368
|
+
puts "executed '#{@command_selected.name}' successfully : #{response.data}"
|
369
|
+
end
|
370
|
+
else
|
371
|
+
puts "could not execute '#{@command_selected.name}' : #{response.error_text}"
|
372
|
+
$logger.error "#{response.error_text} : #{response.error_detail}"
|
373
|
+
end
|
374
|
+
reset_to_command_mode
|
375
|
+
rescue
|
376
|
+
puts "got an error : #{$!}"
|
377
|
+
raise
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
## the following methods are overridden from ShellBackend
|
382
|
+
|
383
|
+
def process_input(command_line)
|
384
|
+
$logger.debug "processing input '#{command_line}'"
|
385
|
+
|
386
|
+
if (@command_selected) then
|
387
|
+
# we're in parameter processing mode - so check which parameter
|
388
|
+
# we've got now and switch modes if necessary
|
389
|
+
|
390
|
+
# we might have been waiting for multiple param values - check if the user finished
|
391
|
+
# adding values by selecting an empty string as value
|
392
|
+
if (@current_param.allows_multiple_values and command_line == "") then
|
393
|
+
$logger.debug "finished multiple parameter input mode for param #{@current_param.name}"
|
394
|
+
@missing_params.shift
|
395
|
+
execute_command_if_possible
|
396
|
+
else
|
397
|
+
accepted_params = add_parameter_value(command_line)
|
398
|
+
if accepted_params
|
399
|
+
# stop asking for more values if
|
400
|
+
# a) the parameter does not allow more than one value
|
401
|
+
# b) the user entered a wildcard parameter that has been expanded to multiple values
|
402
|
+
if (! @current_param.allows_multiple_values or accepted_params.length > 1) then
|
403
|
+
$logger.debug "finished parameter input mode for param #{@current_param.name}"
|
404
|
+
@missing_params.shift
|
405
|
+
execute_command_if_possible
|
406
|
+
else
|
407
|
+
$logger.debug "param '#{@current_param.name}' expects multiple values...deferring mode switching"
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
else
|
412
|
+
# we're waiting for the user to enter a command
|
413
|
+
# we might have a command with params
|
414
|
+
command, *params = command_line.split
|
415
|
+
$logger.debug "got command '#{command}' (params: #{params})"
|
416
|
+
|
417
|
+
# remember what the user specified so far
|
418
|
+
begin
|
419
|
+
@command_selected = @command_broker.get_command(command)
|
420
|
+
#if (@command_selected != nil) then
|
421
|
+
$logger.debug "command_selected: #{@command_selected}"
|
422
|
+
|
423
|
+
# apply the preset param values to this new command
|
424
|
+
# TODO reactivate parameter presets
|
425
|
+
# @command_broker.get_global_parameter_presets().each do |name, values|
|
426
|
+
# # check if the selected command needs this param
|
427
|
+
# @current_param = @command_broker.get_param(command, name)
|
428
|
+
# if @current_param and not @current_param.ignore_global_presets then
|
429
|
+
# $logger.debug "presetting global parameter '#{name}' to '#{values}'"
|
430
|
+
# values.each do |value|
|
431
|
+
# add_parameter_value(value)
|
432
|
+
# end
|
433
|
+
# end
|
434
|
+
# end
|
435
|
+
|
436
|
+
# process the params specified on the command line
|
437
|
+
if (params != nil) then
|
438
|
+
params.each do |param|
|
439
|
+
if param =~ /(.+?)=(.+)/ then
|
440
|
+
# --> named param
|
441
|
+
key = $1
|
442
|
+
value = $2
|
443
|
+
else
|
444
|
+
# TODO if there's only one param, we can always use this as default param (maybe do this in the command?)
|
445
|
+
# --> unnamed param
|
446
|
+
value = param
|
447
|
+
default_param = @command_selected.default_param
|
448
|
+
if default_param != nil then
|
449
|
+
key = default_param.name
|
450
|
+
$logger.debug "collecting value '#{value}' for default param '#{default_param.name}'"
|
451
|
+
else
|
452
|
+
$logger.info "ignoring param '#{value}' because there's no default param"
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
if key then
|
457
|
+
begin
|
458
|
+
@current_param = @command_selected.get_param(key)
|
459
|
+
add_parameter_value(value)
|
460
|
+
rescue RHCP::RhcpException => ex
|
461
|
+
puts ex.to_s
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
$logger.debug "selected command #{@command_selected.name}"
|
468
|
+
execute_command_if_possible
|
469
|
+
rescue RHCP::RhcpException => ex
|
470
|
+
puts "#{ex}"
|
471
|
+
end
|
472
|
+
end
|
473
|
+
rescue => err
|
474
|
+
$logger.error err
|
475
|
+
puts "exception raised: #{err.to_s}"
|
476
|
+
end
|
477
|
+
|
478
|
+
def complete(word = "")
|
479
|
+
$logger.debug "collection completion values for '#{word}'"
|
480
|
+
|
481
|
+
if (@command_selected) then
|
482
|
+
# TODO include partial_value here?
|
483
|
+
props = @command_selected.params[@current_param.name].get_lookup_values()
|
484
|
+
#props = @command_broker.get_lookup_values(@command_selected.name, @current_param.name)
|
485
|
+
else
|
486
|
+
props = @command_broker.get_command_list.values.map{|command| command.name}.sort
|
487
|
+
end
|
488
|
+
|
489
|
+
proposal_list = props.map { |p| "'#{p}'" }.join(" ")
|
490
|
+
$logger.debug "completion proposals: #{proposal_list}"
|
491
|
+
|
492
|
+
prefix = word
|
493
|
+
props.select{|name|name[0...(prefix.size)] == prefix}
|
494
|
+
end
|
495
|
+
|
496
|
+
def show_banner
|
497
|
+
puts @banner
|
498
|
+
end
|
499
|
+
|
500
|
+
def set_prompt(string)
|
501
|
+
@prompt = "#{string ? string + " " : ""}$ "
|
502
|
+
end
|
503
|
+
|
504
|
+
def process_ctrl_c
|
505
|
+
puts ""
|
506
|
+
if @command_selected then
|
507
|
+
reset_to_command_mode
|
508
|
+
else
|
509
|
+
Kernel.exit
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# responsible for what should happen when the user interacts with the shell
|
2
|
+
# provides tab completion options and processes the user's input
|
3
|
+
class ShellBackend
|
4
|
+
|
5
|
+
# is called whenever the user submits a command (hitting enter)
|
6
|
+
def process_input(command_line)
|
7
|
+
raise "not implemented in ShellBackend!"
|
8
|
+
end
|
9
|
+
|
10
|
+
# is called whenever the user requests tab completion
|
11
|
+
# should return an array of completion proposals
|
12
|
+
def complete(word)
|
13
|
+
raise "not implemented in ShellBackend!"
|
14
|
+
end
|
15
|
+
|
16
|
+
# is called by the shell to get the prompt that should be displayed
|
17
|
+
def prompt
|
18
|
+
">"
|
19
|
+
end
|
20
|
+
|
21
|
+
def process_ctrl_c
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def show_banner
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/start_shell.sh
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
#
|
2
|
+
# To change this template, choose Tools | Templates
|
3
|
+
# and open the template in the editor.
|
4
|
+
|
5
|
+
|
6
|
+
$:.unshift File.join(File.dirname(__FILE__),'..','lib')
|
7
|
+
|
8
|
+
require 'test/unit'
|
9
|
+
require 'rhcp_shell_backend'
|
10
|
+
|
11
|
+
require 'rubygems'
|
12
|
+
require 'rhcp'
|
13
|
+
require 'logger'
|
14
|
+
|
15
|
+
require 'setup_test_registry'
|
16
|
+
|
17
|
+
class RhcpShellBackendTest < Test::Unit::TestCase
|
18
|
+
|
19
|
+
# Backend that "remembers" everything it ever printed to the user
|
20
|
+
class BackendMock < RHCPShellBackend
|
21
|
+
|
22
|
+
def initialize(broker, on_receive)
|
23
|
+
super(broker)
|
24
|
+
@on_receive = on_receive
|
25
|
+
end
|
26
|
+
|
27
|
+
def process_input(line)
|
28
|
+
$stdout.puts "[user] #{line}"
|
29
|
+
super(line)
|
30
|
+
end
|
31
|
+
|
32
|
+
def puts(what)
|
33
|
+
$stdout.puts "[shell] #{what}"
|
34
|
+
@on_receive.call(what)
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_prompt(new_prompt)
|
38
|
+
$stdout.puts "[shell: switching prompt to '#{new_prompt}']"
|
39
|
+
super(new_prompt)
|
40
|
+
end
|
41
|
+
|
42
|
+
def complete(word = "")
|
43
|
+
result = super(word)
|
44
|
+
proposal_list = result.map { |p| "'#{p}'" }.join(" ")
|
45
|
+
$stdout.puts "[completion proposals : #{proposal_list}]"
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
def setup
|
52
|
+
# set up a local broker that we'll use for testing
|
53
|
+
# TODO do something about this - it shouldn't be necessary to instantiate this beforehand
|
54
|
+
$logger = Logger.new($stdout)
|
55
|
+
@log = Array.new()
|
56
|
+
@backend = BackendMock.new($broker, self.method(:add_to_log))
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_to_log(new_line)
|
60
|
+
#puts "adding to log (old size : #{@log.size}): *****#{new_line}*****"
|
61
|
+
@log << new_line
|
62
|
+
# TODO CAREFUL: if you un-comment the following line, @log will get screwed up
|
63
|
+
#puts "log now : >>#{@log.join("\n")}<<"
|
64
|
+
end
|
65
|
+
|
66
|
+
def assert_received(expected)
|
67
|
+
assert_equal(expected, @log.slice(- expected.length, expected.length))
|
68
|
+
# clean the log so that assert_no_error works
|
69
|
+
#@log.clear
|
70
|
+
end
|
71
|
+
|
72
|
+
def assert_no_error
|
73
|
+
assert_equal 0, @log.select { |line|
|
74
|
+
/error/i =~ line || /exception/i =~ line || /failed/i =~ line
|
75
|
+
}.size
|
76
|
+
end
|
77
|
+
|
78
|
+
def assert_log_contains(stuff)
|
79
|
+
is_ok = @log.grep(/#{stuff}/).size > 0
|
80
|
+
puts "checking log for #{stuff}"
|
81
|
+
assert is_ok, "log should contain '#{stuff}', but it doesn't : >>#{@log.join("\n")}<<"
|
82
|
+
end
|
83
|
+
|
84
|
+
def assert_prompt(expected)
|
85
|
+
if (expected == '')
|
86
|
+
assert_equal "$ ", @backend.prompt
|
87
|
+
else
|
88
|
+
assert_equal "#{expected} $ ", @backend.prompt
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_banner
|
93
|
+
@backend.banner = "This is a test backend"
|
94
|
+
@backend.show_banner
|
95
|
+
assert_received [ "This is a test backend" ]
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_simple_execute
|
99
|
+
@backend.process_input "test"
|
100
|
+
assert_received [ "executed 'test' successfully : 42" ]
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_invalid_command
|
104
|
+
@backend.process_input "does not exist"
|
105
|
+
assert_received [ "no such command : does" ]
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_missing_mandatory_param
|
109
|
+
@backend.process_input "reverse"
|
110
|
+
assert_prompt 'reverse.input'
|
111
|
+
@backend.process_input "zaphod"
|
112
|
+
assert_received [ "executed 'reverse' successfully : dohpaz" ]
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_completion
|
116
|
+
@backend.process_input "reverse"
|
117
|
+
assert_prompt 'reverse.input'
|
118
|
+
assert_equal [ "zaphod", "beeblebrox" ], @backend.complete
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_command_completion
|
122
|
+
commands = $broker.get_command_list.values.map { |command| command.name }
|
123
|
+
# we should have all remote commands plus "help" and "exit"
|
124
|
+
commands << "help"
|
125
|
+
commands << "exit"
|
126
|
+
commands << "detail"
|
127
|
+
assert_equal commands.sort, @backend.complete.sort
|
128
|
+
assert_no_error
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_params_on_command_line
|
132
|
+
@backend.process_input "reverse input=zaphod"
|
133
|
+
assert_received [ "executed 'reverse' successfully : dohpaz" ]
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_invalid_param_value
|
137
|
+
@backend.process_input "reverse input=bla"
|
138
|
+
assert_received [ "invalid value 'bla' for parameter 'input'" ]
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_multi_params
|
142
|
+
@backend.process_input "cook"
|
143
|
+
assert_prompt 'cook.ingredient'
|
144
|
+
@backend.process_input "mascarpone"
|
145
|
+
@backend.process_input "chocolate"
|
146
|
+
@backend.process_input ""
|
147
|
+
assert_no_error
|
148
|
+
assert_received [ "executed 'cook' successfully : mascarpone chocolate" ]
|
149
|
+
end
|
150
|
+
|
151
|
+
# if the user is in command mode, he should be able to exit to command mode
|
152
|
+
# by pressing ctrl+c
|
153
|
+
def test_abort_param_mode
|
154
|
+
@backend.process_input "cook"
|
155
|
+
assert_prompt 'cook.ingredient'
|
156
|
+
@backend.process_ctrl_c
|
157
|
+
assert_prompt ''
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_failing_command
|
161
|
+
@backend.process_input "perpetuum_mobile"
|
162
|
+
assert_received [ "could not execute 'perpetuum_mobile' : don't know how to do this" ]
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_wildcard_support
|
166
|
+
@backend.process_input "cook ingredient=m*"
|
167
|
+
assert_received [ "executed 'cook' successfully : mascarpone marzipan" ]
|
168
|
+
end
|
169
|
+
|
170
|
+
def test_preprocess_without_lookup_values
|
171
|
+
@backend.process_input "test thoroughly=yes"
|
172
|
+
assert_no_error
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_wildcard_ranges
|
176
|
+
@backend.process_input "echo input=string01..05"
|
177
|
+
assert_received [ "executed 'echo' successfully : string01 string02 string03 string04 string05" ]
|
178
|
+
|
179
|
+
@backend.process_input "echo input=string17..20"
|
180
|
+
assert_received [ "executed 'echo' successfully : string17 string18 string19 string20" ]
|
181
|
+
end
|
182
|
+
|
183
|
+
def test_help
|
184
|
+
@backend.process_input "help"
|
185
|
+
assert_log_contains "The following commands are available"
|
186
|
+
$broker.get_command_list.values.each do |command|
|
187
|
+
assert_log_contains command.name
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_help_command
|
192
|
+
@log.clear
|
193
|
+
@backend.process_input "help cook"
|
194
|
+
puts "LOG >>#{@log}<<"
|
195
|
+
assert_log_contains "Syntax:"
|
196
|
+
assert_log_contains "cook ingredient=<value>, ingredient=<value2>, ..."
|
197
|
+
end
|
198
|
+
|
199
|
+
def test_help_with_default_param
|
200
|
+
@backend.process_input "help help"
|
201
|
+
puts "LOG >>#{@log}<<"
|
202
|
+
assert_log_contains "Syntax:"
|
203
|
+
assert_log_contains "help [<command>]"
|
204
|
+
end
|
205
|
+
|
206
|
+
def test_default_param
|
207
|
+
@backend.process_input "reverse zaphod"
|
208
|
+
assert_received [ "executed 'reverse' successfully : dohpaz" ]
|
209
|
+
end
|
210
|
+
|
211
|
+
# unnamed params should be ignored if no default params are specified
|
212
|
+
def test_unnamed_param_without_default_param
|
213
|
+
@backend.process_input "echo bla"
|
214
|
+
assert_received [ "executed 'echo' successfully : hello world" ]
|
215
|
+
end
|
216
|
+
|
217
|
+
def test_complete_without_lookup_values
|
218
|
+
@backend.process_input "length"
|
219
|
+
assert_prompt "length.input"
|
220
|
+
assert_equal [], @backend.complete
|
221
|
+
end
|
222
|
+
|
223
|
+
def test_table
|
224
|
+
# TODO write a separate test for this stuff
|
225
|
+
p $broker.get_command("build_a_table")
|
226
|
+
@backend.process_input "build_a_table"
|
227
|
+
# assert_received [
|
228
|
+
# "Zaphod\tBeeblebrox",
|
229
|
+
# "Arthur\tDent",
|
230
|
+
# "Prostetnik\tYoltz(?)"
|
231
|
+
# ]
|
232
|
+
end
|
233
|
+
|
234
|
+
# TODO test behaviour after an internal error occurred in the shell (e.g. some problem while formatting the result)
|
235
|
+
|
236
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rhcp'
|
3
|
+
|
4
|
+
|
5
|
+
broker = RHCP::Broker.new()
|
6
|
+
broker.clear()
|
7
|
+
broker.register_command RHCP::Command.new("test", "just a test command",
|
8
|
+
lambda { |req,res|
|
9
|
+
42
|
10
|
+
}
|
11
|
+
).add_param(RHCP::CommandParam.new("thoroughly", "an optional param",
|
12
|
+
{
|
13
|
+
:mandatory => false
|
14
|
+
}
|
15
|
+
)
|
16
|
+
)
|
17
|
+
broker.register_command RHCP::Command.new("echo", "prints a string",
|
18
|
+
lambda { |req,res|
|
19
|
+
strings = req.has_param_value("input") ? req.get_param_value("input") : [ "hello world" ]
|
20
|
+
result = Array.new
|
21
|
+
strings.each do |s|
|
22
|
+
puts s
|
23
|
+
result << s
|
24
|
+
end
|
25
|
+
result.join(" ")
|
26
|
+
}
|
27
|
+
).add_param(RHCP::CommandParam.new("input", "an optional param",
|
28
|
+
{
|
29
|
+
:mandatory => false,
|
30
|
+
:allows_multiple_values => true,
|
31
|
+
:lookup_method => lambda {
|
32
|
+
values = Array.new()
|
33
|
+
1.upto(20) do |i|
|
34
|
+
values << "string#{sprintf("%02d", i)}"
|
35
|
+
end
|
36
|
+
values
|
37
|
+
}
|
38
|
+
}
|
39
|
+
)
|
40
|
+
)
|
41
|
+
broker.register_command RHCP::Command.new("reverse", "reversing input strings",
|
42
|
+
lambda { |req,res|
|
43
|
+
req.get_param_value("input").reverse
|
44
|
+
}
|
45
|
+
).add_param(RHCP::CommandParam.new("input", "the string to reverse",
|
46
|
+
{
|
47
|
+
:lookup_method => lambda { [ "zaphod", "beeblebrox" ] },
|
48
|
+
:mandatory => true,
|
49
|
+
:is_default_param => true
|
50
|
+
}
|
51
|
+
)
|
52
|
+
)
|
53
|
+
broker.register_command RHCP::Command.new("cook", "cook something nice out of some ingredients",
|
54
|
+
lambda { |req,res|
|
55
|
+
ingredients = req.get_param_value("ingredient").join(" ")
|
56
|
+
puts "cooking something with #{ingredients}"
|
57
|
+
ingredients
|
58
|
+
}
|
59
|
+
).add_param(RHCP::CommandParam.new("ingredient", "something to cook with",
|
60
|
+
{
|
61
|
+
:lookup_method => lambda { [ "mascarpone", "chocolate", "eggs", "butter", "marzipan" ] },
|
62
|
+
:allows_multiple_values => true,
|
63
|
+
:mandatory => true
|
64
|
+
}
|
65
|
+
)
|
66
|
+
)
|
67
|
+
broker.register_command RHCP::Command.new("perpetuum_mobile", "this command will fail",
|
68
|
+
lambda { |req,res|
|
69
|
+
raise "don't know how to do this"
|
70
|
+
}
|
71
|
+
)
|
72
|
+
broker.register_command RHCP::Command.new("length", "returns the length of a string",
|
73
|
+
lambda { |req,res|
|
74
|
+
req.get_param_value("input").length
|
75
|
+
}
|
76
|
+
).add_param(RHCP::CommandParam.new("input", "the string to reverse",
|
77
|
+
{
|
78
|
+
:mandatory => true,
|
79
|
+
:is_default_param => true
|
80
|
+
}
|
81
|
+
)
|
82
|
+
)
|
83
|
+
|
84
|
+
command = RHCP::Command.new("list_stuff", "this command lists stuff",
|
85
|
+
lambda { |req,res|
|
86
|
+
[ "peace", "aquaeduct", "education" ]
|
87
|
+
}
|
88
|
+
)
|
89
|
+
command.mark_as_read_only()
|
90
|
+
command.result_hints[:display_type] = "list"
|
91
|
+
broker.register_command command
|
92
|
+
|
93
|
+
command2 = RHCP::Command.new("build_a_table", "this command returns tabular data",
|
94
|
+
lambda { |req,res|
|
95
|
+
[
|
96
|
+
{ :first_name => "Zaphod", :last_name => "Beeblebrox", :heads => 2, :character => "dangerous" },
|
97
|
+
{ :first_name => "Arthur", :last_name => "Dent", :heads => 1, :character => "harmless (mostly)" },
|
98
|
+
{ :first_name => "Prostetnik", :last_name => "Yoltz (?)", :heads => 1, :character => "ugly" }
|
99
|
+
]
|
100
|
+
}
|
101
|
+
)
|
102
|
+
command2.mark_as_read_only()
|
103
|
+
command2.result_hints[:display_type] = "table"
|
104
|
+
command2.result_hints[:overview_columns] = [ "first_name", "last_name" ]
|
105
|
+
broker.register_command command2
|
106
|
+
|
107
|
+
p broker.get_command("build_a_table")
|
108
|
+
|
109
|
+
$broker = broker
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rhcp_shell
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Philipp T.
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-23 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hoe
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.8.3
|
24
|
+
version:
|
25
|
+
description: FIX (describe your package)
|
26
|
+
email:
|
27
|
+
- philipp@hitchhackers.net
|
28
|
+
executables: []
|
29
|
+
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files:
|
33
|
+
- History.txt
|
34
|
+
- Manifest.txt
|
35
|
+
- README.txt
|
36
|
+
files:
|
37
|
+
- History.txt
|
38
|
+
- Manifest.txt
|
39
|
+
- README.txt
|
40
|
+
- Rakefile
|
41
|
+
- README
|
42
|
+
- lib/base_shell.rb
|
43
|
+
- lib/rhcp_shell.rb
|
44
|
+
- lib/rhcp_shell_backend.rb
|
45
|
+
- lib/shell_backend.rb
|
46
|
+
- start_shell.sh
|
47
|
+
- test/rhcp_shell_backend_test.rb
|
48
|
+
- test/setup_test_registry.rb
|
49
|
+
has_rdoc: true
|
50
|
+
homepage: FIX (url)
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options:
|
53
|
+
- --main
|
54
|
+
- README.txt
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
version:
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
version:
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project: rhcp
|
72
|
+
rubygems_version: 1.3.1
|
73
|
+
signing_key:
|
74
|
+
specification_version: 2
|
75
|
+
summary: FIX (describe your package)
|
76
|
+
test_files: []
|
77
|
+
|